Blob Blame History Raw
/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */

/* libmwaw
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2002 William Lachance (wrlach@gmail.com)
* Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net)
* Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
* Copyright (C) 2006, 2007 Andrew Ziem
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/

#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <sstream>

#include <librevenge/librevenge.h>

#include "MWAWCell.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPictMac.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSpreadsheetEncoder.hxx"
#include "MWAWSpreadsheetListener.hxx"
#include "MWAWTextListener.hxx"

#include "RagTimeParser.hxx"
#include "RagTimeStruct.hxx"

#include "RagTimeSpreadsheet.hxx"

/** Internal: the structures of a RagTimeSpreadsheet */
namespace RagTimeSpreadsheetInternal
{
struct Cell;
//! Internal: date/time format of a RagTimeSpreadsheet
struct DateTime {
  //! constructor
  DateTime()
    : m_isDate(true)
    , m_DTFormat("")
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, DateTime const &dt)
  {
    o << dt.m_DTFormat;
    if (!dt.m_isDate) o << "[time],";
    else o << ",";
    return o;
  }
  //! true if this is a date field
  bool m_isDate;
  //! the date time format
  std::string m_DTFormat;
};

//! Internal: cell number format of a RagTimeSpreadsheet (SpVa block)
struct CellFormat {
  //! constructor
  CellFormat()
    : m_numeric()
    , m_dateTime()
    , m_align(MWAWCell::HALIGN_DEFAULT)
    , m_rotation(0)
    , m_flags(0)
    , m_extra("")
  {
  }
  //! update the cell format if needed
  void update(Cell &cell) const;
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, CellFormat const &form)
  {
    if (form.m_rotation) o << "rot=" << form.m_rotation << ",";
    if (form.m_flags&2) o << "protect[all],";
    if (form.m_flags&4) o << "protect[format,formula],";
    if (form.m_flags&0x8) o << "invisible,";
    if (form.m_flags&0x20) o << "zero[dontshow],";
    if (form.m_flags&0x40) o << "prec[variable],"; // precision depend on display
    if (form.m_flags&0x80) o << "print[no],";

    if (form.m_flags&0xFF11) o << "fl=" << std::hex << (form.m_flags&0xFF11) << std::dec << ",";
    o << form.m_extra;
    return o;
  }
  //! the numeric format
  MWAWCell::Format m_numeric;
  //! the date/time format
  DateTime m_dateTime;
  //! the cell's alignment
  MWAWCell::HorizontalAlignment m_align;
  //! the rotation angle
  int m_rotation;
  //! some flags
  int m_flags;
  //! extra data (for debugging)
  std::string m_extra;
};

//! Internal: cell border of a RagTimeSpreadsheet (SpVa block)
struct CellBorder {
  //! constructor
  CellBorder()
    : m_extra("")
  {
  }
  //! returns true if the cell has some border
  bool hasBorders() const
  {
    return !m_borders[0].isEmpty() && !m_borders[1].isEmpty();
  }
  //! update the cell border if need
  void update(Cell &cell) const;
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, CellBorder const &border)
  {
    for (int i=0; i<2; ++i) {
      if (border.m_borders[i]!=MWAWBorder())
        o << (i==0 ? "top=" : "left=") << border.m_borders[i] << ",";
    }
    o << border.m_extra;
    return o;
  }
  //! the top and left border
  MWAWBorder m_borders[2];
  //! extra data
  std::string m_extra;
};

//! Internal: extra cell format of a RagTimeSpreadsheet (SpCe block)
struct CellExtra {
  //! constructor
  CellExtra()
    : m_isTransparent(false)
    , m_color(MWAWColor::white())
    , m_extra("")
  {
  }
  //! update the cell color if need
  void update(Cell &cell) const;
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, CellExtra const &st)
  {
    if (st.m_isTransparent) o << "noColor,";
    else if (!st.m_color.isWhite()) o << "color[back]=" << st.m_color << ",";
    o << st.m_extra;
    return o;
  }
  //! true if the cell is transparent
  bool m_isTransparent;
  //! the background color
  MWAWColor m_color;
  //! extra data
  std::string m_extra;
};

//! Internal: header of a complex block of a RagTimeSpreadsheet
struct ComplexBlock {
  //! constructor
  ComplexBlock()
    : m_zones()
    , m_intList()
  {
  }
  //! a small zone of a ComplexBlock
  struct Zone {
    //! constructor
    Zone()
      : m_pos(0)
    {
      for (auto &data : m_data) data=0;
    }
    //! operator<<
    friend std::ostream &operator<<(std::ostream &o, Zone const &z)
    {
      o << "pos=" << std::hex << z.m_pos << std::dec << ",";
      for (int i=0; i<3; ++i) {
        if (!z.m_data[i]) continue;
        char const *wh[]= {"firstVal","N", "Nsub"};
        o << wh[i] << "=" << z.m_data[i] << ",";
      }
      return o;
    }
    //! the zone position
    long m_pos;
    //! three unknown int
    int m_data[3];
  };
  //! the list of zone
  std::vector<Zone> m_zones;
  //! a list of unknown counter
  std::vector<int> m_intList;
};

//! Internal: a cell of a RagTimeSpreadsheet
struct Cell final : public MWAWCell {
  //! constructor
  explicit Cell(MWAWVec2i pos=MWAWVec2i(0,0))
    : MWAWCell()
    , m_content()
    , m_textEntry()
    , m_rotation(0)
  {
    setPosition(pos);
  }
  ///! destructor
  ~Cell() final;
  //! test if we can use or not the formula
  bool validateFormula()
  {
    if (m_content.m_formula.empty()) return false;
    for (auto &instr : m_content.m_formula) {
      bool ok=true;
      // fixme: cell to other spreadsheet
      if (instr.m_type==MWAWCellContent::FormulaInstruction::F_Cell ||
          instr.m_type==MWAWCellContent::FormulaInstruction::F_CellList)
        ok=instr.m_sheet.empty();
      // fixme: replace operator or by function
      else if (instr.m_type==MWAWCellContent::FormulaInstruction::F_Function)
        ok=(instr.m_content!="Or"&&instr.m_content!="And"&&instr.m_content!="Not");
      if (!ok) {
        m_content.m_formula.resize(0);
        return false;
      }
    }
    return true;
  }
  //! the cell content
  MWAWCellContent m_content;
  //! the text entry if the cell is a zone of text zone
  MWAWEntry m_textEntry;
  //! the content's rotation angle
  int m_rotation;
};

Cell::~Cell()
{
}

void CellFormat::update(Cell &cell) const
{
  MWAWCell::Format format=cell.getFormat();
  if (format.m_format==MWAWCell::F_NUMBER)
    format=m_numeric;
  else if (format.m_format==MWAWCell::F_DATE) {
    format.m_DTFormat=m_dateTime.m_DTFormat;
    if (!m_dateTime.m_isDate)
      format.m_format=MWAWCell::F_TIME;
  }
  cell.setFormat(format);
  cell.setHAlignment(m_align);
  if (m_flags&6) cell.setProtected(true);
  cell.m_rotation=m_rotation;
}

void CellBorder::update(Cell &cell) const
{
  if (!m_borders[0].isEmpty()) cell.setBorders(libmwaw::TopBit, m_borders[0]);
  if (!m_borders[1].isEmpty()) cell.setBorders(libmwaw::LeftBit, m_borders[1]);
}

void CellExtra::update(Cell &cell) const
{
  if (!m_isTransparent) cell.setBackgroundColor(m_color);
}

//! Internal: a spreadsheet's zone of a RagTimeSpreadsheet
struct Spreadsheet {
  //! a map a cell sorted by row
  typedef std::map<MWAWVec2i,Cell,MWAWVec2i::PosSizeLtY> Map;
  //! constructor
  Spreadsheet()
    : m_rows(0)
    , m_columns(0)
    , m_widthDefault(72)
    , m_widthCols()
    , m_heightDefault(12)
    , m_heightRows()
    , m_cellsBegin(0)
    , m_cellsMap()
    , m_rowPositionsList()
    , m_name("Sheet0")
    , m_isSent(false)
  {
  }
  //! returns the row size in point
  float getRowHeight(int row) const
  {
    if (row>=0&&row<static_cast<int>(m_heightRows.size())&&m_heightRows[size_t(row)]>0)
      return m_heightRows[size_t(row)];
    return m_heightDefault;
  }
  //! returns the height of a row in point and updated repeated row
  float getRowHeight(int row, int &numRepeated) const
  {
    float res=getRowHeight(row);
    numRepeated=1;
    if (row<0 || row>=static_cast<int>(m_heightRows.size()))
      numRepeated=1000;
    else {
      for (auto r=size_t(row+1); r<m_heightRows.size(); ++r) {
        float nextH=getRowHeight(row+1);
        if (nextH<res || nextH>res)
          break;
        ++numRepeated;
      }
    }
    return res;
  }
  /** returns the columns dimension in point */
  std::vector<float> getColumnsWidth() const
  {
    auto numCols=size_t(getRightBottomPosition()[0]+1);
    std::vector<float> res(numCols, float(m_widthDefault));
    if (m_widthCols.size()<numCols)
      numCols=m_widthCols.size();
    for (size_t i=0; i<numCols; ++i) {
      if (m_widthCols[i] > 0)
        res[i] = m_widthCols[i];
    }
    return res;
  }
  /** returns the spreadsheet dimension */
  MWAWVec2i getRightBottomPosition() const
  {
    MWAWVec2i res(0,0);
    for (auto it : m_cellsMap) {
      Cell const &cell=it.second;
      if (cell.position()[0] >= res[0])
        res[0]=cell.position()[0]+1;
      if (cell.position()[1] >= res[1])
        res[1]=cell.position()[1]+1;
    }
    return res;
  }
  /** the number of row */
  int m_rows;
  /** the number of col */
  int m_columns;
  /** the default column width */
  float m_widthDefault;
  /** the column size in points */
  std::vector<float> m_widthCols;
  /** the default row height */
  float m_heightDefault;
  /** the row height in points */
  std::vector<float> m_heightRows;
  /** the positions of the cells in the file */
  long m_cellsBegin;
  /** the map cell position to not empty cells */
  Map m_cellsMap;
  /** the positions of row in the file */
  std::vector<long> m_rowPositionsList;
  /** the sheet name */
  std::string m_name;
  /** true if the sheet is sent to the listener */
  mutable bool m_isSent;
};

////////////////////////////////////////
//! Internal: the state of a RagTimeSpreadsheet
struct State {
  //! constructor
  State()
    : m_version(-1)
    , m_numericFormatList()
    , m_dateTimeList()
    , m_cellFontList()
    , m_cellFormatList()
    , m_cellBorderList()
    , m_cellExtraList()
    , m_cellDEList()
    , m_idSpreadsheetMap()
  {
  }

  //! the file version
  mutable int m_version;
  //! a list of numeric format
  std::vector<MWAWCell::Format> m_numericFormatList;
  //! a list dateTimeFormatId -> dateTimeFormat;
  std::vector<DateTime> m_dateTimeList;
  //! a list SpTe -> font
  std::vector<MWAWFont> m_cellFontList;
  //! a list SpVaId -> cellFormat
  std::vector<CellFormat> m_cellFormatList;
  //! a list SpBoId -> cellBorder
  std::vector<CellBorder> m_cellBorderList;
  //! a list SpCeId -> cellExtra
  std::vector<CellExtra> m_cellExtraList;
  //! a list SpDEId -> unknown data
  std::vector<std::string> m_cellDEList;
  //! map id -> spreadsheet
  std::map<int, std::shared_ptr<Spreadsheet> > m_idSpreadsheetMap;
};

}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
RagTimeSpreadsheet::RagTimeSpreadsheet(RagTimeParser &parser)
  : m_parserState(parser.getParserState())
  , m_state(new RagTimeSpreadsheetInternal::State)
  , m_mainParser(&parser)
{
}

RagTimeSpreadsheet::~RagTimeSpreadsheet()
{ }

int RagTimeSpreadsheet::version() const
{
  if (m_state->m_version < 0)
    m_state->m_version = m_parserState->m_version;
  return m_state->m_version;
}

////////////////////////////////////////////////////////////
// resource
////////////////////////////////////////////////////////////
bool RagTimeSpreadsheet::getDateTimeFormat(int dtId, std::string &dtFormat) const
{
  if (dtId<0||dtId>=int(m_state->m_dateTimeList.size())) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::getDateTimeFormat: can not find the date/format %d\n", dtId));
    return false;
  }
  dtFormat=m_state->m_dateTimeList[size_t(dtId)].m_DTFormat;
  return !dtFormat.empty();
}

bool RagTimeSpreadsheet::readNumericFormat(MWAWEntry &entry)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  if (pos<=0 || !input->checkPosition(pos+2+0x26)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readNumericFormat: the position seems bad\n"));
    return false;
  }
  entry.setParsed(true);
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  f << "Entries(" << entry.type() << ")[" << entry.id() << "]:";
  auto dSz=static_cast<int>(input->readULong(2));
  long endPos=pos+2+dSz;
  auto headerSz=static_cast<int>(input->readULong(2));
  auto fSz=static_cast<int>(input->readULong(2));
  auto N=static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  if (headerSz<0x20 || fSz<6 || dSz<headerSz+long(N+1)*fSz || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readNumericFormat: the size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  input->seek(pos+2+headerSz, librevenge::RVNG_SEEK_SET);
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  std::set<long> posSet;
  posSet.insert(endPos);
  for (int i=0; i<=N; ++i) {
    pos=input->tell();
    f.str("");
    f << entry.type() << "-" << i << ":";
    auto val=static_cast<int>(input->readLong(2)); // 0 (except last)
    if (val) f << "f0=" << val << ",";
    val=static_cast<int>(input->readLong(2)); // always 1 ?
    if (val!=1) f << "f1=" << val << ",";
    auto fPos=static_cast<int>(input->readULong(2));
    f << "pos[def]=" << std::hex << entry.begin()+2+fPos << std::dec << ",";
    posSet.insert(entry.begin()+2+fPos);
    input->seek(pos+fSz, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }

  for (auto it=posSet.begin(); it!=posSet.end();) {
    pos=*(it++);
    if (pos>=endPos) break;
    long nextPos=it==posSet.end() ? endPos : *it;
    f.str("");
    f << entry.type() << "[def]:";
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    int depl[4];
    f << "depl=[";
    for (int &i : depl) {
      i=static_cast<int>(input->readULong(1));
      if (i) f << i << ",";
      else f << "_,";
    }
    f << "],";
    if (entry.id()==0) {
      MWAWCell::Format format;
      format.m_format=MWAWCell::F_NUMBER;
      format.m_numberFormat=MWAWCell::F_NUMBER_DECIMAL;
      if (depl[0]<16 || pos+depl[0]>endPos) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readNumericFormat: the first zone seems bad\n"));
        f << "#header,";
      }
      else {
        auto val=static_cast<int>(input->readULong(1));
        if (val==1) {
          format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
          f << "standart,";
        }
        else if (val) f << "#standart=" << val << ",";
        format.m_digits=static_cast<int>(input->readULong(1));
        for (int i=0; i<10; ++i) {
          val=static_cast<int>(input->readULong(1));
          if (val) f << "f" << i << "=" << val << ",";
        }
      }
      if (depl[0]<depl[1] && pos+depl[1]<endPos) {
        input->seek(pos+depl[0], librevenge::RVNG_SEEK_SET);
        ascFile.addDelimiter(input->tell(),'|');
        f << "format=[";
        for (int i=depl[0]; i<depl[1]; ++i) {
          auto c=static_cast<int>(input->readULong(1));
          switch (c) {
          case 1: // end
            break;
          case 6:
            f << "#";
            break;
          case 7: // int
            f << "0";
            break;
          case 9: // fix
            f << ",";
            break;
          case 0xa:
            format.m_thousandHasSeparator=true;
            f << " ";
            break;
          case 0xc:
            format.m_numberFormat=MWAWCell::F_NUMBER_SCIENTIFIC;
            f << "E+";
            break;
          case 0xe:
            format.m_numberFormat=MWAWCell::F_NUMBER_PERCENT;
            f << "%";
            break;
          case 0xf:
            format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
            f << "[standart]";
            break;
          default:
            if (c<0x1f) {
              f << "#[" << std::hex << c << std::dec << "]";
            }
            else {
              format.m_numberFormat=MWAWCell::F_NUMBER_CURRENCY; // only probable
              f << char(c);
            }
          }
        }
        f << "],";
      }
      else {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readNumericFormat: the compress format zone seems bad\n"));
        f << "#format,";
      }
      f << format;
      m_state->m_numericFormatList.push_back(format);
    }
    else if (depl[3]<8 || pos+depl[3]>endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readNumericFormat: the first zone seems bad\n"));
      f << "#header,";
      m_state->m_dateTimeList.push_back(RagTimeSpreadsheetInternal::DateTime());
    }
    else {
      f << "depl2=[";
      for (int i=0; i<4; ++i) { // f0=small number, f1=f2=f3=0
        auto val=static_cast<int>(input->readULong(1));
        if (val) f << val << ",";
        else f << "_,";
      }
      f << "],";
      bool ok=true, isDate=false;;
      std::string dtFormat("");
      for (int i=8; i<depl[3]; ++i) {
        auto c=static_cast<int>(input->readULong(1));
        switch (c) {
        case 0: // probably next field...
          break;
        case 1: // end
          break;
        case 7:
          dtFormat+="%S";
          break;
        case 9:
          dtFormat+="%M";
          break;
        case 0xa:
          dtFormat+="%H";
          break;
        case 0xc:
        case 0xd: // with 2 digits
          dtFormat+="%d";
          isDate=true;
          break;
        case 0xe: // checkme
          dtFormat+="%a";
          isDate=true;
          break;
        case 0xf:
          dtFormat+="%A";
          isDate=true;
          break;
        case 0x10:
        case 0x11: // with 2 digits
          dtFormat+="%m";
          isDate=true;
          break;
        case 0x12:
          dtFormat+="%b";
          isDate=true;
          break;
        case 0x13:
          dtFormat+="%B";
          isDate=true;
          break;
        case 0x14:
          dtFormat+="%y";
          isDate=true;
          break;
        case 0x15:
          dtFormat+="%Y";
          isDate=true;
          break;
        default:
          if (c<0x1f) {
            std::stringstream s;
            s << "#[" << std::hex << c << std::dec << "]";
            dtFormat+=s.str();
            ok=false;
          }
          else
            dtFormat+=char(c);
        }
      }
      f << "format=[" << dtFormat << "],";
      if (ok) {
        RagTimeSpreadsheetInternal::DateTime dt;
        dt.m_DTFormat=dtFormat;
        dt.m_isDate=isDate;
        m_state->m_dateTimeList.push_back(dt);
      }
      else
        m_state->m_dateTimeList.push_back(RagTimeSpreadsheetInternal::DateTime());
    }
    // fixme: read the 3 other potential header zones
    headerSz=depl[3];
    if ((headerSz&1)) ++headerSz;
    input->seek(pos+headerSz, librevenge::RVNG_SEEK_SET);
    auto cSz=static_cast<int>(input->readULong(1));
    if (headerSz<4||cSz<=0||pos+headerSz+1+cSz!=nextPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readNumericFormat: can not read a format\n"));
      f << "###";
    }
    else {
      ascFile.addDelimiter(input->tell()-1,'|');
      std::string name("");
      for (int i=0; i<cSz; ++i) name += char(input->readULong(1));
      f << name << ",";
    }
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  return true;
}

bool RagTimeSpreadsheet::readResource(MWAWEntry &entry)
{
  if (entry.begin()<=0) return false;

  std::string const &type=entry.type();
  if (type.length()!=8 || type.compare(0,6,"rsrcSp")!=0)
    return false;
  if (entry.type()=="rsrcSpDI")
    return readRsrcSpDI(entry);
  if (entry.type()=="rsrcSpDo")
    return readRsrcSpDo(entry);

  auto resType=RagTimeStruct::ResourceList::Undef;
  for (int i=RagTimeStruct::ResourceList::SpBo; i<=RagTimeStruct::ResourceList::SpVa; ++i) {
    std::string name("rsrc");
    name+=RagTimeStruct::ResourceList::getName(RagTimeStruct::ResourceList::Type(i));
    if (entry.type()!=name) continue;
    resType=RagTimeStruct::ResourceList::Type(i);
    break;
  }
  if (resType==RagTimeStruct::ResourceList::Undef)
    return false;

  entry.setParsed(true);
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << "Entries(" << entry.type() << ")[" << entry.id() << "]:";
  RagTimeStruct::ResourceList zone;
  if (!zone.read(input, entry)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: the size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  f << zone;
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  input->seek(zone.m_dataPos, librevenge::RVNG_SEEK_SET);
  if (zone.m_type!=resType && zone.m_dataNumber) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: find unexpected data size\n"));
    for (int i=0; i<zone.m_dataNumber; ++i) {
      pos=input->tell();
      f.str("");
      f << entry.type() << "-" << i << ":##";
      input->seek(pos+zone.m_dataSize, librevenge::RVNG_SEEK_SET);
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
    }
  }
  else {
    int val;
    for (int i=0; i<zone.m_dataNumber; ++i) {
      pos=input->tell();
      f.str("");
      switch (resType) {
      case RagTimeStruct::ResourceList::SpBo: {
        RagTimeSpreadsheetInternal::CellBorder borders;
        libmwaw::DebugStream f2;
        val=static_cast<int>(input->readLong(2)); // always 0
        if (val) f << "f0=" << val << ",";
        val=static_cast<int>(input->readLong(2));
        if (val!=1) f << "used=" << val << ",";
        for (int j=0; j<2; ++j) {
          MWAWBorder border;
          val=static_cast<int>(input->readULong(2));
          f2.str("");
          // val&0x8000: def in cell
          if (val&0x4000) if (j) f2 << "third[bottom],";
          switch (val&3) {
          case 0:
            f2 << "pos[exterior],";
            break;
          case 1:
            f2 << "pos[interior],";
            break;
          case 2: // normal
            break;
          default:
            f2 << "##pos=3,";
            break;
          }
          val &= 0x3FFC;
          if (val) f2 << "fl=" << std::hex << val << std::dec << ",";
          border.m_width=double(input->readLong(4))/65536.;
          val=static_cast<int>(input->readLong(2));
          if (val && !m_mainParser->getColor(val-1, border.m_color)) {
            MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: can not find border color %d\n", val-1));
            f2 << "#color=" << val-1 << ",";
          }
          border.m_extra=f2.str();
          borders.m_borders[j]=border;
        }
        borders.m_extra=f.str();
        m_state->m_cellBorderList.push_back(borders);
        f.str("");
        f << borders;
        break;
      }
      case RagTimeStruct::ResourceList::SpCe: {
        RagTimeSpreadsheetInternal::CellExtra extra;
        val=static_cast<int>(input->readLong(2)); // always 0
        if (val) f << "f0=" << val << ",";
        val=static_cast<int>(input->readLong(2));
        if (val!=1) f << "used=" << val << ",";
        val=static_cast<int>(input->readULong(2));
        if (val&0x8000) extra.m_isTransparent=true;
        if (val&0x4000) f << "bottom[color],";
        if (val&0x2000) f << "top[color],";
        val &= 0x1FFF;
        if (val) f << "fl=" << std::hex << val << std::dec << ",";
        val=static_cast<int>(input->readLong(2));
        if (val && !m_mainParser->getColor(val-1, extra.m_color)) {
          MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: can not find color %d\n", val-1));
          f << "#color=" << val-1 << ",";
        }
        extra.m_extra=f.str();
        m_state->m_cellExtraList.push_back(extra);
        f.str("");
        f << extra;
        break;
      }
      case RagTimeStruct::ResourceList::SpDE:
        for (int j=0; j<2; ++j) { // f0=2, f1=1-b
          val=static_cast<int>(input->readLong(2));
          if (val) f << "f" << j << "=" << val << ",";
        }
        for (int j=0; j<2; ++j) { // two big number ?
          val=static_cast<int>(input->readULong(2));
          if (val) f << "g" << j << "=" << std::hex << val << std::dec << ",";
        }
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f2=" << val << ",";
        m_state->m_cellDEList.push_back(f.str());
        break;
      case RagTimeStruct::ResourceList::SpTe: {
        MWAWFont font;
        val=static_cast<int>(input->readLong(2)); // always 0
        if (val) f << "f0=" << val << ",";
        val=static_cast<int>(input->readLong(2));
        if (val!=1) f << "used=" << val << ",";
        val=static_cast<int>(input->readLong(2));
        if (val>0 && !m_mainParser->getCharStyle(val-1, font)) {
          MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: can not find a char format\n"));
          f << "##id[CharId]=" << val << ",";
        }
        val=static_cast<int>(input->readLong(1));
        if (val) font.setDeltaLetterSpacing(float(val));
        val=static_cast<int>(input->readLong(1)); // 1|5
        if (val!=1) f << "f1=" << val << ",";
        val=static_cast<int>(input->readLong(1));
        if (val) font.set(MWAWFont::Script(-float(val),librevenge::RVNG_POINT));
        val=static_cast<int>(input->readULong(1));
        f << "lang=" << val << ",";
        font.m_extra=f.str();
        m_state->m_cellFontList.push_back(font);
        f.str("");
        f << font.getDebugString(m_parserState->m_fontConverter);
        break;
      }
      case RagTimeStruct::ResourceList::SpVa: {
        RagTimeSpreadsheetInternal::CellFormat format;
        val=static_cast<int>(input->readLong(2)); // always 0
        if (val) f << "f0=" << val << ",";
        val=static_cast<int>(input->readLong(2));
        if (val!=1) f << "used=" << val << ",";
        format.m_flags=static_cast<int>(input->readULong(2)); // small number
        val=static_cast<int>(input->readLong(2));
        if (val<=0 || val>int(m_state->m_numericFormatList.size())) {
          MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: can not find a numeric format\n"));
          f << "##id[NumFormat]=" << val << ",";
        }
        else {
          format.m_numeric=m_state->m_numericFormatList[size_t(val-1)];
          if (val!=1) // default
            f << format.m_numeric << ",";
        }
        val=static_cast<int>(input->readLong(2));
        if (val<=0 || val>int(m_state->m_dateTimeList.size())) {
          MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: can not find a date/time format\n"));
          f << "##id[DTFormat]=" << val << ",";
        }
        else {
          format.m_dateTime=m_state->m_dateTimeList[size_t(val-1)];
          if (val!=1)
            f << format.m_dateTime << ",";
        }
        val=static_cast<int>(input->readLong(1));
        switch (val) {
        case 1: // normal
          break;
        case 2:
          format.m_align=MWAWCell::HALIGN_LEFT;
          f << "left,";
          break;
        case 3:
          format.m_align=MWAWCell::HALIGN_CENTER;
          f << "center,";
          break;
        case 4:
          format.m_align=MWAWCell::HALIGN_RIGHT;
          f << "right,";
          break;
        case 5:
          f << "comma[justify],";
          break;
        case 6:
          f << "repeat,";
          break;
        default:
          f << "#align=" << val << ",";
          break;
        }
        val=static_cast<int>(input->readLong(1));
        if (val>=1&&val<=4)
          format.m_rotation=90*(val-1);
        else
          f << "#rotation=" << val << ",";

        format.m_extra=f.str();
        m_state->m_cellFormatList.push_back(format);
        f.str("");
        f << format;
        break;
      }
      case RagTimeStruct::ResourceList::BuSl:
      case RagTimeStruct::ResourceList::BuGr:
      case RagTimeStruct::ResourceList::gray:
      case RagTimeStruct::ResourceList::colr:
      case RagTimeStruct::ResourceList::res_:
      case RagTimeStruct::ResourceList::Undef:
#if !defined(__clang__)
      default:
#endif
        break;
      }
      std::string data=f.str();
      f.str("");
      f << entry.type() << "-" << i << ":" << data;
      if (input->tell()>pos+zone.m_dataSize) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: can not find the entry size seems bad\n"));
        f << "###";
        ascFile.addPos(pos);
        ascFile.addNote(f.str().c_str());
        return false;
      }
      input->seek(pos+zone.m_dataSize, librevenge::RVNG_SEEK_SET);
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
    }
  }
  // last field is always empty
  pos=input->tell();
  ascFile.addPos(pos);
  ascFile.addNote("_");
  input->seek(pos+zone.m_dataSize, librevenge::RVNG_SEEK_SET);

  if (input->tell()!=zone.m_endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readResource: find some extra data\n"));
    f.str("");
    f << entry.type() << "-end:";
    ascFile.addPos(input->tell());
    ascFile.addNote(f.str().c_str());
  }
  return true;
}

bool RagTimeSpreadsheet::readRsrcSpDI(MWAWEntry &entry)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  if (pos<=0 || !input->checkPosition(pos+2+0x20)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readRsrcSpDI: the position seems bad\n"));
    return false;
  }
  entry.setParsed(true);
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << "Entries(" << entry.type() << ")[" << entry.id() << "]:";
  auto dSz=static_cast<int>(input->readULong(2));
  long endPos=pos+2+dSz;
  auto headerSz=static_cast<int>(input->readULong(2));
  auto fSz=static_cast<int>(input->readULong(2));
  auto N=static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  if (headerSz<0x20 || fSz<0x8 || dSz<headerSz+long(N+1)*fSz || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readRsrcSpDI: the size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  input->seek(pos+2+headerSz, librevenge::RVNG_SEEK_SET);
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  std::set<long> posSet;
  posSet.insert(endPos);
  for (int i=0; i<=N; ++i) {
    pos=input->tell();
    f.str("");
    f << entry.type() << "-" << i << ":";
    auto val=static_cast<int>(input->readLong(2)); // 0 (except last)
    if (val) f << "f0=" << val << ",";
    auto fPos=static_cast<int>(input->readULong(2));
    if (fPos) {
      f << "pos[def]=" << std::hex << entry.begin()+2+fPos << std::dec << ",";
      posSet.insert(entry.begin()+2+fPos);
    }
    input->seek(pos+fSz, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  for (auto &dPos : posSet) {
    if (dPos>=endPos) break;
    f.str("");
    f << entry.type() << "[data]:";
    ascFile.addPos(dPos);
    ascFile.addNote(f.str().c_str());
  }
  return true;
}

bool RagTimeSpreadsheet::readRsrcSpDo(MWAWEntry &entry)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  if (pos<=0 || !input->checkPosition(pos+2+0x4a)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readRsrcSpDo: the position seems bad\n"));
    return false;
  }
  entry.setParsed(true);
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << "Entries(rsrcSpDo)[" << entry.id() << "]:";
  auto dSz=static_cast<int>(input->readULong(2));
  long endPos=pos+2+dSz;
  if (dSz<0x4a || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readRsrcSpDo: the size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  for (int i=0; i<2; ++i) { // f1=0|80
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << std::hex << val << std::dec << ",";
  }
  int val0=0; // to make clang analyzer happy
  for (int i=0; i<10; ++i) { // find g0=3+10*k, g1=10+g0, g2=10+g1, g3=10+g2, other 0|3+10*k1
    auto val = static_cast<int>(input->readLong(4));
    if (i==0) {
      val0=val;
      f << "g0=" << val << ",";
    }
    else if (i<4 && val!=val0+10*i)
      f << "g" << i << "=" << val << ",";
    else if (i>=4 && val)
      f << "g" << i << "=" << val << ",";
  }
  for (int i=0; i<9; ++i) {
    auto val = static_cast<int>(input->readULong(2));
    static int const expected[]= {0,0,0,0x64,0x3ff5,0x8312,0x6e97,0x8d4f,0xdf3b};
    if (val!=expected[i])
      f << "h" << i << "=" << std::hex << val << std::dec << ",";
  }
  int const numVal= int(endPos-4-input->tell())/2;
  for (int i=0; i<numVal; ++i) { // k0=small int, k1=k0+1, k2=k3=k4=0, k5=0|b7|e9|f3
    auto val = static_cast<int>(input->readLong(2));
    if (val) f << "k" << i << "=" << val << ",";
  }
  input->seek(endPos-4, librevenge::RVNG_SEEK_SET);
  f << "id?=" << std::hex << input->readULong(4) << std::dec << ","; // a big number
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return true;
}


////////////////////////////////////////////////////////////
// Intermediate level
////////////////////////////////////////////////////////////
bool RagTimeSpreadsheet::readBlockHeader(MWAWEntry const &entry, RagTimeSpreadsheetInternal::ComplexBlock &block)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  long endPos=entry.end();
  if (pos<=0 || entry.length()<6 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readBlockHeader: the position seems bad\n"));
    return false;
  }

  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  ascFile.addPos(endPos);
  ascFile.addNote("_");

  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(" << entry.type() << "):";
  auto N=static_cast<int>(input->readLong(2));
  f << "N=" << N << ",";
  auto dSz=static_cast<int>(input->readULong(4));
  if (dSz<N*2+10 || pos+2+dSz>endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readBlockHeader: the data size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  auto val=static_cast<int>(input->readLong(2));
  f << "f0=" << val << ",";
  f << "flags=[";
  for (int i=0; i<=N; ++i) {
    val=static_cast<int>(input->readLong(2));
    block.m_intList.push_back(val);
    if (val) f << val << ",";
    else f << "_,";
  }
  f << "],";
  if (dSz!=N*2+10) {
    ascFile.addDelimiter(input->tell(),'|');
    input->seek(pos+dSz, librevenge::RVNG_SEEK_SET);
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  pos=input->tell();
  f.str("");
  f << entry.type() << ":";
  N=static_cast<int>(input->readULong(2));
  f << "N[zones]=" << N << ",";
  if (pos+2+10*N>endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readBlockHeader: the zone block seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return true;
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  for (int i=0; i<N; ++i) {
    RagTimeSpreadsheetInternal::ComplexBlock::Zone zone;
    pos=input->tell();
    f.str("");
    f << entry.type() << "-Z" << i << "[def]:";
    for (auto &data : zone.m_data) data=static_cast<int>(input->readLong(2));
    auto dataPos=long(input->readULong(4));
    if (dataPos<N*2+10 || dataPos>entry.length()) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readBlockHeader: the zone position seems bad\n"));
      f << "###dataPos" << std::hex << entry.begin()+dataPos << std::dec << ",";
    }
    else
      zone.m_pos=entry.begin()+dataPos;
    block.m_zones.push_back(zone);
    f << zone;
    input->seek(pos+10, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }

  return true;
}

bool RagTimeSpreadsheet::readPositionsList(MWAWEntry const &entry, std::vector<long> &posList, long &lastDataPos)
{
  posList.resize(0);
  if (version()<2) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readPositionsList: must not be called for v1-2... file\n"));
    return false;
  }

  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=input->tell();
  long endPos=entry.end();
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << entry.type() << "[PosList]:";
  auto dSz=static_cast<int>(input->readULong(4));
  auto N=static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  if (dSz<10+2*N || pos+dSz>endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readPositionsList: can not find the second block size\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  auto dataSize=long(input->readULong(2));
  f << "lPos=" << std::hex << pos+dSz+dataSize << std::dec << ",";
  if (dataSize&1) ++dataSize;
  lastDataPos=pos+dSz+dataSize;
  if (lastDataPos>endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readPositionsList: the last position seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  f << "pos=[";
  for (int i=0; i<N; ++i) {
    long newPos=pos+dSz+long(input->readULong(2));
    f << std::hex << newPos << std::dec << ",";
    if (newPos>lastDataPos)  {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readPositionsList: find some bad position\n"));
      f << "###]";
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      input->seek(pos+dSz, librevenge::RVNG_SEEK_SET);
      return true;
    }
    posList.push_back(newPos);
  }
  f << "],";
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// spreadsheet zone v3...
////////////////////////////////////////////////////////////
bool RagTimeSpreadsheet::readSpreadsheet(MWAWEntry &entry)
{
  if (version()<2) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheet: must not be called for v1-2... file\n"));
    return false;
  }
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  int dataFieldSize=m_mainParser->getZoneDataFieldSize(entry.id());
  if (pos<=0 || !input->checkPosition(pos+dataFieldSize+0x64)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheet: the position seems bad\n"));
    return false;
  }
  entry.setParsed(true);
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << "Entries(SpreadsheetZone):";
  auto dSz=static_cast<int>(input->readULong(dataFieldSize));
  long endPos=pos+dataFieldSize+dSz;
  if (dSz<0x62 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheet: the size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  int val;
  for (int i=0; i<6; ++i) { // f0=0, f1=4|6|44, f2=1-8, f3=1-f, f4=1-5, f5=1-3
    val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  for (int i=0; i<6; ++i) // g0~40, g1=g2=2, g3~16, g4=[b-10], g5=[8-c]|800d
    f << "g" << i << "=" << double(input->readLong(4))/65536. << ",";
  long zoneBegin[11];
  zoneBegin[10]=endPos;
  for (int i=0; i<10; ++i) {
    zoneBegin[i]=long(input->readULong(4));
    if (!zoneBegin[i]) continue;
    f << "zone" << i << "=" << std::hex << pos+dataFieldSize+zoneBegin[i] << std::dec << ",";
    if (pos+2+zoneBegin[i]>endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheet: the zone %d seems bad\n",i));
      zoneBegin[i]=0;
      f << "###";
      continue;
    }
    zoneBegin[i]+=pos+dataFieldSize;
  }
  f << "fl?=["; // or some big number
  for (int i=0; i<8; ++i) {
    val=static_cast<int>(input->readULong(2));
    if (val)
      f << std::hex << val << std::dec << ",";
    else
      f << "_,";
  }
  f << "],";
  for (int i=0; i<3; ++i) { // h0=0-4, h1=h2=0
    val=static_cast<int>(input->readULong(2));
    if (val)
      f << "h" << i << "=" << val << ",";
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  // now read the different zone, first set the endPosition
  for (int i=9; i>=0; --i) {
    if (zoneBegin[i]==0)
      zoneBegin[i]=zoneBegin[i+1];
    else if (zoneBegin[i]>zoneBegin[i+1]) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheet: the zone %d seems bad(II)\n",i));
      zoneBegin[i]=zoneBegin[i+1];
    }
  }
  std::shared_ptr<RagTimeSpreadsheetInternal::Spreadsheet> sheet(new RagTimeSpreadsheetInternal::Spreadsheet);
  for (int i=0; i<10; ++i) {
    if (zoneBegin[i+1]<=zoneBegin[i]) continue;
    MWAWEntry zone;
    zone.setBegin(zoneBegin[i]);
    zone.setEnd(zoneBegin[i+1]);
    zone.setId(i);

    char const *what[]= {
      "SpreadsheetContent", "SpreadsheetCFormat", "SpreadsheetFormula", "SpreadsheetCondition",
      "SpreadsheetCUseIn", "SpreadsheetUnknown5", "SpreadsheetUnknown6", "SpreadsheetCExtraUseIn",
      "SpreadsheetCRef", "SpreadsheetUnknown9",
    };
    zone.setType(what[i]);

    bool ok=true;
    switch (i) {
    case 0: // content
    case 1: // format
    case 2: // formula
    case 3: // condition
    case 4: // list of cell which used which have a ref the current cell
      ok=readSpreadsheetComplexStructure(zone, *sheet);
      if (ok && i<=2)
        m_state->m_idSpreadsheetMap[entry.id()]=sheet;
      break;
    case 5:
    // case 6: never seens
    case 7: // list of extra spreadsheet which have a ref to the current cell
    case 8: // link to cell in other extra spreadsheet(we must probably read it)
      ok=readSpreadsheetSimpleStructure(zone, *sheet);
      break;
    case 9:
      ok=readSpreadsheetZone9(zone, *sheet);
      break;
    default:
      ok=false;
      break;
    }
    if (ok) continue;
    f.str("");
    f << "Entries(" << zone.type() << "):";
    ascFile.addPos(zoneBegin[i]);
    ascFile.addNote(f.str().c_str());
    // SpreadsheetZone-9: sz+N+N*14
  }
  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetCellDimension(MWAWVec2i const &cellPos, long endPos, RagTimeSpreadsheetInternal::Spreadsheet &sheet)
{
  if (cellPos[0] && cellPos[1]) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellDimension: expect cellPos[0] or cellPos[1] = 0\n"));
    return false;
  }
  MWAWInputStreamPtr input = m_parserState->m_input;
  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;

  long pos=input->tell();
  f << "SpreadsheetContent-";
  if (cellPos[1]==0) {
    f << "colDim[" << cellPos[0]-1 << "]:";
    if (pos+16>endPos || cellPos[0]<=0) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellDimension: can not read a row height\n"));
      f << "##";
    }
    else {
      f << "dim=[";
      // col dim, followed by some margins?, no sure if v3~15/16 is or not a dim
      for (int j=0; j<4; ++j) {
        auto dim=static_cast<uint32_t>(input->readULong(4));
        f << float(dim&0x7FFFFFFF)/65536.f;
        if (dim&0x80000000) f << "/h";
        f << ",";
        if (j) continue;
        if (cellPos[0]>int(sheet.m_widthCols.size()))
          sheet.m_widthCols.resize(size_t(cellPos[0]),0);
        sheet.m_widthCols[size_t(cellPos[0]-1)]=float(dim&0x7FFFFFFF)/65536.f;
      }
      f << "],";
    }
    if ((input->tell()+1==endPos && input->readLong(1)!=0) || input->tell()!=endPos)
      f<<"#";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return true;
  }

  f << "-rowDim[" << cellPos[1] << "]:";
  if (pos+8>endPos||cellPos[1]<=0) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellDimension: can not read a row height\n"));
    f << "##";
  }
  else {
    f << "dim=[";
    for (int j=0; j<2; ++j) { // row dim, followed by cell height
      auto dim=static_cast<uint32_t>(input->readULong(4));
      f << float(dim&0x7FFFFFFF)/65536.f;
      if (dim&0x80000000) f << "/h";
      f << ",";
      if (j) continue;
      if (cellPos[1]>int(sheet.m_heightRows.size()))
        sheet.m_heightRows.resize(size_t(cellPos[1]),0);
      sheet.m_heightRows[size_t(cellPos[1]-1)]=float(dim&0x7FFFFFFF)/65536.f;
    }
    f << "],";
  }
  if ((input->tell()+1==endPos && input->readLong(1)!=0) || input->tell()!=endPos)
    f<<"#";
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetCellContent(RagTimeSpreadsheetInternal::Cell &cell, long endPos)
{
  MWAWVec2i const &cellPos=cell.position();
  if (cellPos[0]<0 || cellPos[1]<0) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellContent: expect cellPos[0] and cellPos[1] >= 0\n"));
    return false;
  }

  MWAWInputStreamPtr input = m_parserState->m_input;
  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;

  long pos=input->tell();

  f << "SpreadsheetContent-C" << cellPos<< "]:";
  bool ok=true;
  auto format=cell.getFormat();
  MWAWCellContent &content=cell.m_content;
  int val, type=static_cast<int>(input->readULong(1));
  switch (type) {
  case 0: // not frequent, but can happens
    break;
  case 0x40:
  case 0x44: // find with 44244131
  case 0x80:
    if (type==0x80)
      f << "Nan[eval],";
    else
      f << "Nan[circ],";
    format.m_format=MWAWCell::F_NUMBER;
    format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
    content.m_contentType=MWAWCellContent::C_NUMBER;
    content.setValue(std::numeric_limits<double>::quiet_NaN());
    break;
  case 0x81:
    f << "float81,";
    MWAW_FALLTHROUGH;
  case 3:
    MWAW_FALLTHROUGH;
  case 1: {
    if (type==3) {
      format.m_format=MWAWCell::F_DATE; // we need the format to choose between date and time
      f << "date/time,";
    }
    else {
      format.m_format=MWAWCell::F_NUMBER;
      format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
    }
    content.m_contentType=MWAWCellContent::C_NUMBER;
    if (pos+11>endPos) {
      ok=false;
      break;
    }
    double res;
    bool isNan;
    if (input->readDouble10(res, isNan)) {
      content.setValue(res);
      f << res << ",";
    }
    else
      f << "#value,";
    break;
  }
  case 0x24:
    f << "text2,";
    MWAW_FALLTHROUGH;
  case 4: {
    format.m_format=MWAWCell::F_TEXT;
    content.m_textEntry.setBegin(input->tell());
    content.m_textEntry.setEnd(endPos);

    std::string text("");
    for (int j=0; j<endPos-1-pos; ++j) {
      auto c=char(input->readLong(1));
      if (c==0) {
        content.m_textEntry.setEnd(input->tell()-1);
        break;
      }
      text+=c;
    }
    f << text << ",";
    break;
  }
  case 5:
    if (pos+2>endPos) {
      ok=false;
      break;
    }
    val=static_cast<int>(input->readLong(1));
    f << val << ",";
    break;
  case 6:
  case 0x14: {
    f << "textZone,";
    cell.m_textEntry.setBegin(input->tell());
    cell.m_textEntry.setEnd(endPos);
    cell.m_textEntry.setId(m_mainParser->getNewZoneId());
    cell.m_textEntry.setType("SpreadsheetText");
    format.m_format=MWAWCell::F_TEXT;
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
    break;
  }
  case 0x51:
    f << "long51,";
    MWAW_FALLTHROUGH;
  case 0x11: // or 2 int?
    if (pos+5>endPos) {
      ok=false;
      break;
    }
    val=static_cast<int>(input->readLong(4));
    format.m_format=MWAWCell::F_NUMBER;
    format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
    content.m_contentType=MWAWCellContent::C_NUMBER;
    content.setValue(double(val));
    f << val << ",";
    break;
  default:
    ok=false;
    break;
  }
  cell.setFormat(format);
  if (!ok) f<< "##";
  else if ((input->tell()+1==endPos && input->readLong(1)!=0) || input->tell()!=endPos)
    f<<"#";
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return ok;
}

bool RagTimeSpreadsheet::readSpreadsheetCellFormat(MWAWVec2i const &cellPos, long endPos, RagTimeSpreadsheetInternal::Cell &cell)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=input->tell();
  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;

  f << "SpreadsheetCFormat-";
  if (cellPos[1]==0) f << "col:";
  else if (cellPos[0]==0) f << "row:";
  else f << cell.position() << ":";
  if (pos+8>endPos) {
    f << "###";
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellFormat: a block definition seems bad\n"));
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return true;
  }
  for (int j=0; j<4; ++j) {
    auto val=static_cast<int>(input->readULong(2));
    if (val==0) {
      if (cellPos[0] && cellPos[1]) {
        char const *wh[] = {"SpTe","SpVa", "SpBo", "SpCe"};
        f << wh[j] << "=#undef,";
      }
      continue;
    }
    switch (j) {
    case 0: {
      if (val<=0 || val >static_cast<int>(m_state->m_cellFontList.size())) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellFormat: find unexpected SpTe index\n"));
        f << "id[SpTe]=##" << val-1 << ",";
        break;
      }

      MWAWFont const &font=m_state->m_cellFontList[size_t(val-1)];
      cell.setFont(font);
      if (val==1) break;
      f << "te=[" << font.getDebugString(m_parserState->m_fontConverter) << "],";
      break;
    }
    case 1: {
      if (val<=0 || val>static_cast<int>(m_state->m_cellFormatList.size())) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellFormat: find unexpected SpVa index\n"));
        f << "id[SpVa]=##" << val-1 << ",";
        break;
      }
      auto const &cFormat=m_state->m_cellFormatList[size_t(val-1)];
      cFormat.update(cell);
      if (val==1) break;
      f << "va=[" << cFormat << "],";
      break;
    }
    case 2: {
      if (val <= 0 || val>static_cast<int>(m_state->m_cellBorderList.size())) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellFormat: find unexpected SpBoindex\n"));
        f << "id[SpBo]=##" << val-1 << ",";
        break;
      }
      auto const &borders=m_state->m_cellBorderList[size_t(val-1)];
      if (val==1) break;
      borders.update(cell);
      f << "bo=[" << borders <<  "],";
      break;
    }
    case 3: {
      if (val<=0 || val>static_cast<int>(m_state->m_cellExtraList.size())) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellFormat: find unexpected SpCe index\n"));
        f << "id[SpCe]=##" << val-1 << ",";
        break;
      }
      auto const &extras=m_state->m_cellExtraList[size_t(val-1)];
      extras.update(cell);
      if (val==1) break;
      f << "ce=[" << extras << "],";
      break;
    }
    default:
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellFormat: find unexpected type\n"));
      f << "###";
      break;
    }

  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetZone9(MWAWEntry const &entry, RagTimeSpreadsheetInternal::Spreadsheet &/*sheet*/)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  long endPos=entry.end();
  if (pos<=0 || entry.length()<3 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetZone9: the position seems bad\n"));
    return false;
  }

  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  ascFile.addPos(endPos);
  ascFile.addNote("_");

  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(" << entry.type() << "):";
  auto dSz=static_cast<int>(input->readULong(4));
  auto N=static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  if (pos+4+dSz>endPos||dSz!=2+14*N) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetZone9: the data size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  for (int i=0; i<N; ++i) {
    pos=input->tell();
    f.str("");
    f << entry.type() << "-A" << i << ":";
    input->seek(pos+14, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  pos=input->tell();
  if (pos!=endPos) {
    f.str("");
    f << entry.type() << "-extra:";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetSimpleStructure(MWAWEntry const &entry, RagTimeSpreadsheetInternal::Spreadsheet &/*sheet*/)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  long endPos=entry.end();
  if (pos<=0 || entry.length()<8 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetSimpleStructure: the position seems bad\n"));
    return false;
  }

  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  ascFile.addPos(endPos);
  ascFile.addNote("_");

  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(" << entry.type() << "):";
  auto dSz=static_cast<int>(input->readULong(4));
  auto headerSz=static_cast<int>(input->readULong(2));
  if (pos+4+dSz>endPos||headerSz<18||headerSz>dSz) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetSimpleStructure: the data size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  auto fSz=static_cast<int>(input->readULong(2));
  auto N=static_cast<int>(input->readULong(2));
  f << "N=" << N << "[" << fSz << "],";
  for (int i=0; i<2; ++i) { // f0=4|c
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  auto ptrSz=static_cast<int>(input->readLong(2));
  if (ptrSz==2||ptrSz==4) f << "ptr[sz]=" << ptrSz << ",";
  else if (ptrSz) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetSimpleStructure: the ptr size seems bad\n"));
    f << "###ptrSz" << ptrSz << ",";
  }
  auto dataSz=static_cast<int>(input->readLong(4));
  if (headerSz<18 || headerSz+long(N+1)*fSz+dataSz>dSz || fSz < 0) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetSimpleStructure: the data size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  ascFile.addDelimiter(input->tell(),'|');
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  input->seek(pos+4+headerSz, librevenge::RVNG_SEEK_SET);
  std::set<long> dataPosSet;
  for (int i=0; i<=N; ++i) {
    pos=input->tell();
    f.str("");
    f << entry.type() << "-A" << i << ":";
    if (ptrSz) {
      const long eSz = long(input->readULong(ptrSz));
      long dPos = eSz < std::numeric_limits<long>::max()-entry.begin()-4 ? entry.begin()+4+eSz : std::numeric_limits<long>::max();
      f << "pos=" << std::hex << dPos << std::dec << ",";
      if (dPos>endPos) {
        f << "###";
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetSimpleStructure: the data pos seems bad\n"));
      }
      else if (dPos<endPos)
        dataPosSet.insert(dPos);
    }
    input->seek(pos+fSz, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  f.str("");
  f << entry.type() << "-data:";
  for (auto dPos : dataPosSet) {
    ascFile.addPos(dPos);
    ascFile.addNote(f.str().c_str());
  }
  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetComplexStructure(MWAWEntry const &entry, RagTimeSpreadsheetInternal::Spreadsheet &sheet)
{
  RagTimeSpreadsheetInternal::ComplexBlock block;
  if (!readBlockHeader(entry, block)) return false;

  MWAWInputStreamPtr input = m_parserState->m_input;
  long endPos=entry.end();

  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  ascFile.addPos(endPos);
  ascFile.addNote("_");

  /* first sort data by first value ( as we need to parse first the
     column definition before parsing a cell content ) */
  std::map<int,size_t> sortZones;
  for (size_t z=0; z< block.m_zones.size(); ++z) {
    int const fValue=block.m_zones[z].m_data[0];
    if (sortZones.find(fValue)!=sortZones.end()) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetComplexStructure: oops, a zone with firstVal=%d already exists\n", fValue));
      f.str("");
      f << entry.type() << "-unparsed:###";
      ascFile.addPos(block.m_zones[z].m_pos);
      ascFile.addNote(f.str().c_str());
    }
    else
      sortZones[fValue]=z;
  }

  long lastEndPos=input->tell(); // end of last block to check for unparsed data
  for (auto it : sortZones) {
    size_t const z=it.second;
    auto &contentZone=block.m_zones[z];
    long contentEndPos=(block.m_zones.size()>z+1 && block.m_zones[z+1].m_pos<entry.begin()) ?
                       block.m_zones[z+1].m_pos : endPos;

    long pos=contentZone.m_pos;
    if (pos<entry.begin() || pos>=contentEndPos) continue;
    input->seek(pos, librevenge::RVNG_SEEK_SET);

    std::vector<long> posList;
    long lastDataPos;
    if (!readPositionsList(entry, posList, lastDataPos))
      return true;

    int const numCellsByRows=contentZone.m_data[2];
    if (!posList.empty() && numCellsByRows<=0) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetComplexStructure: can not find determine the cell's positions\n"));
      continue;
    }

    int row=contentZone.m_data[0]-1, prevActiveRow=-1;
    for (size_t i=0; i<posList.size(); ++i) {
      f.str("");
      int col=int(i)%numCellsByRows;
      if (int(i)/numCellsByRows!=prevActiveRow) {
        while (1) {
          if (++row >= int(block.m_intList.size()) || row<0) {
            MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetComplexStructure: can not find determine a cell's row\n"));
            f << "###";
            break;
          }
          if (!block.m_intList[size_t(row)]) continue;
          // checkme: do we need to check the other block.m_intList[size_t(row)] values
          break;
        }
        prevActiveRow=int(i)/numCellsByRows;
      }

      long zEndPos=(i+1==posList.size()) ? lastDataPos:posList[i+1];
      if (!posList[i] || posList[i]>=zEndPos) continue;

      input->seek(posList[i], librevenge::RVNG_SEEK_SET);
      MWAWVec2i cellPos(col,row);
      bool ok=true;
      RagTimeSpreadsheetInternal::Cell emptyCell;
      RagTimeSpreadsheetInternal::Cell *cell = nullptr;
      if (col>0 && row>0 && entry.id()<=2) {
        if (sheet.m_cellsMap.find(cellPos-MWAWVec2i(1,1))==sheet.m_cellsMap.end())
          sheet.m_cellsMap[cellPos-MWAWVec2i(1,1)]=RagTimeSpreadsheetInternal::Cell();
        cell=&sheet.m_cellsMap.find(cellPos-MWAWVec2i(1,1))->second;
      }
      if (!cell) cell=&emptyCell;
      cell->setPosition(cellPos-MWAWVec2i(1,1));
      switch (entry.id()) {
      case 0:
        if (cellPos[0]<=0 || cellPos[1]<=0)
          ok=readSpreadsheetCellDimension(cellPos, zEndPos, sheet);
        else
          ok=readSpreadsheetCellContent(*cell, zEndPos);
        break;
      case 1:
        ok=readSpreadsheetCellFormat(cellPos, zEndPos, *cell);
        break;
      case 2: // formula
      case 3: { // condition
        f << entry.type() << "-C" << col << "x" << row << "]:";
        std::string extra("");
        std::vector<MWAWCellContent::FormulaInstruction> formula;
        auto val=static_cast<int>(input->readULong(1));
        if (val) f << "f0=" << std::hex << val << std::dec << ",";
        ok=readFormula(cellPos-MWAWVec2i(1,1), formula, zEndPos, extra);
        if (ok && entry.id()==2) {
          MWAWCellContent &content=cell->m_content;
          content.m_formula=formula;
          if (cell->validateFormula())
            content.m_contentType=MWAWCellContent::C_FORMULA;
        }
        if (!ok) f << "###";
        f << "formula=[";
        for (auto const &form : formula)
          f << form;
        f << "]";
        if (!extra.empty()) f << ":" << extra;
        f << ",";
        ascFile.addPos(posList[i]);
        ascFile.addNote(f.str().c_str());
        ok=true;
        break;
      }
      // case 4: list of cell which reference this cell, ok to ignore
      default:
        ok=false;
        break;
      }
      if (!ok) {
        f << entry.type() << "-C" << col << "x" << row << "]:";
        if (row==0) f << "col,";
        else if (col==0) f << "row,";

        ascFile.addPos(posList[i]);
        ascFile.addNote(f.str().c_str());
      }
    }
    if (lastDataPos>lastEndPos) lastEndPos=lastDataPos;
  }

  if (lastEndPos<endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetComplexStructure: find some extra data\n"));
    f.str("");
    f << entry.type() << "-extra:###";
    ascFile.addPos(lastEndPos);
    ascFile.addNote(f.str().c_str());
  }
  input->seek(lastEndPos, librevenge::RVNG_SEEK_SET);
  return true;
}

////////////////////////////////////////////////////////////
// spreadsheet zone v2...
////////////////////////////////////////////////////////////
bool RagTimeSpreadsheet::readSpreadsheetV2(MWAWEntry &entry)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  if (pos<=0 || !input->checkPosition(pos+6)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetV2: the position seems bad\n"));
    return false;
  }
  if (version()>=2) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetV2: must not be called for v3... file\n"));
    return false;
  }
  entry.setParsed(true);
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  f << "Entries(SpreadsheetZone):";
  auto dSz=static_cast<int>(input->readULong(2));
  long endPos=pos+2+dSz;
  if (dSz<4 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetV2: the size seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  ascFile.addPos(endPos);
  ascFile.addNote("_");
  long zonesList[2]= {0,endPos};
  for (int i=0; i<2; ++i) {
    long ptr=pos+6+long(input->readULong(2));
    f << "ptr[" << i << "]=" << std::hex << ptr << std::dec << ",";
    if (ptr>=endPos) {
      f << "###";
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetV2: the zone begin seems bad%d\n", i));
      continue;
    }
    zonesList[i]=ptr;
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  std::shared_ptr<RagTimeSpreadsheetInternal::Spreadsheet> sheet(new RagTimeSpreadsheetInternal::Spreadsheet);
  std::stringstream s;
  s << "Sheet" << entry.id();
  sheet->m_name=s.str();
  // first read the last data, which contains the begin of row positions
  MWAWEntry extra;
  extra.setBegin(zonesList[1]);
  extra.setEnd(endPos);
  sheet->m_cellsBegin=zonesList[0];
  if (!readSpreadsheetExtraV2(extra, *sheet))
    return false;

  MWAWEntry cells;
  cells.setBegin(zonesList[0]);
  cells.setEnd(zonesList[1]);
  if (!readSpreadsheetCellsV2(cells, *sheet))
    return false;
  m_state->m_idSpreadsheetMap[entry.id()]=sheet;
  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetCellsV2(MWAWEntry &entry, RagTimeSpreadsheetInternal::Spreadsheet &sheet)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  long endPos=entry.end();
  if (pos<=0 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellsV2: the position seems bad\n"));
    return false;
  }

  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  ascFile.addPos(endPos);
  ascFile.addNote("_");

  int n=0;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  for (size_t row=0; row<sheet.m_rowPositionsList.size(); ++row) {
    pos=sheet.m_rowPositionsList[row];
    long rEndPos=row+1==sheet.m_rowPositionsList.size() ? endPos : sheet.m_rowPositionsList[row+1];
    if (pos<entry.begin() || rEndPos>entry.end()) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellsV2: the position of the row cells %d is odd\n", int(row)));
      continue;
    }
    if (pos+2>=rEndPos) continue;
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    while (1) {
      pos=input->tell();
      if (pos+2>rEndPos) break;
      int col=int(input->readULong(1))-1;
      MWAWVec2i cellPos(col,int(row));
      auto dSz=static_cast<int>(input->readULong(1));
      long zEndPos=pos+6+dSz;
      RagTimeSpreadsheetInternal::Cell cell;
      cell.setPosition(cellPos);
      f.str("");
      f << "Entries(SpreadsheetCell)[" << n++ << "]:";
      if (zEndPos>endPos) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellsV2: problem reading some cells\n"));
        f << "###";
        ascFile.addPos(pos);
        ascFile.addNote(f.str().c_str());
        break;
      }
      else if (!readSpreadsheetCellV2(cell, zEndPos) || cellPos[0]<0||cellPos[1]<0) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellsV2: small pb reading a cell\n"));
        f << "###";
        ascFile.addPos(pos);
        ascFile.addNote(f.str().c_str());
      }
      else if (sheet.m_cellsMap.find(cellPos) != sheet.m_cellsMap.end()) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellsV2: already find a cell in (%d,%d)\n", cellPos[0],cellPos[1]));
        ascFile.addPos(pos);
        ascFile.addNote("###duplicated");
      }
      else
        sheet.m_cellsMap[cellPos]=cell;
      if ((dSz%2)==1) ++zEndPos;
      input->seek(zEndPos, librevenge::RVNG_SEEK_SET);
    }
  }
  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetCellV2(RagTimeSpreadsheetInternal::Cell &cell, long endPos)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=input->tell();
  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;

  f << "Entries(SpreadsheetCell)[C" << cell.position() << "]:";
  if (pos+4>endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: the zone seems too short\n"));
    f << "###";
    ascFile.addPos(pos-2);
    ascFile.addNote(f.str().c_str());
    return false;
  }

  auto val=static_cast<int>(input->readULong(1));
  int type=(val>>4);
  MWAWCell::Format format;
  switch (type) {
  case 0:
    format.m_format=MWAWCell::F_NUMBER;
    f << "empty,";
    break;
  case 3:
    format.m_format=MWAWCell::F_NUMBER;
    format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
    f << "number,";
    break;
  case 7:
    format.m_format=MWAWCell::F_DATE;
    f << "date,";
    break;
  case 9:
    format.m_format=MWAWCell::F_TEXT;
    f << "text,";
    break;
  case 11:
    format.m_format=MWAWCell::F_NUMBER;
    format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
    f << "nan,";
    break;
  default:
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: find unknown type %d\n", type));
    f << "##type=" << type << ",";
    break;
  }
  bool hasFont=(val&1);
  bool hasAlign=(val&2);
  bool hasFormula=(val&4);
  bool hasPreFormula=(val&8);
  if (hasFormula)
    f << "formula,";
  if (hasPreFormula)
    f << "preFormula,";

  val=static_cast<int>(input->readULong(1));
  bool hasNumberFormat=false;
  if (val&0x80) {
    f << "digits[set],";
    val&=0x7F;
    hasNumberFormat=true;
  }
  // fl0&30: no change
  if (val) f << "fl0=" << std::hex << val << std::dec << ",";
  val=static_cast<int>(input->readULong(1));
  if (val&0xF0) { // checkme
    int borders=0;
    f << "bord=";
    if (val&0x10) {
      borders|=libmwaw::LeftBit;
      f << "L";
    }
    if (val&0x20) {
      borders|=libmwaw::RightBit;
      f << "R";
    }
    if (val&0x40) {
      borders|=libmwaw::TopBit;
      f << "T";
    }
    if (val&0x80) {
      borders|=libmwaw::BottomBit;
      f << "B";
    }
    f << ",";
    cell.setBorders(borders, MWAWBorder());
  }
  if (val&0xF) f << "fl1=" << std::hex << (val&0xf) << std::dec << ",";
  val=static_cast<int>(input->readULong(1));
  if (val) f << "fl2=" << std::hex << val << std::dec << ",";
  long actPos;
  if (hasNumberFormat) {
    val=static_cast<int>(input->readULong(1));
    actPos=input->tell();
    bool ok=true, hasDigits=true;
    switch (val>>5) {
    case 1: // unknown
      f << "type1,";
      break;
    case 3:
      f << "currency,";
      format.m_numberFormat=MWAWCell::F_NUMBER_CURRENCY;
      break;
    case 6:
      f << "percent,";
      format.m_numberFormat=MWAWCell::F_NUMBER_PERCENT;
      break;
    case 4:
      f << "scientific,";
      format.m_numberFormat=MWAWCell::F_NUMBER_SCIENTIFIC;
      break;
    case 2:
      f << "decimal,";
      format.m_numberFormat=MWAWCell::F_NUMBER_DECIMAL;
      break;
    case 0:
      hasDigits=false;
      break;
    default:
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: problem read numbering flags\n"));
      f << "##type=" << (val>>5) << ",";
      hasDigits=ok=false;
      break;
    }
    if (ok) {
      val &= 0x1F;
      if (hasDigits && actPos+1>endPos) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: can not read a digit\n"));
        f << "##digits,";
        ok=false;
      }
      else if (hasDigits) {
        auto digits=static_cast<int>(input->readULong(1));
        if (digits&0xC0) {
          f << "digits[high]=" << (digits>>6) << ",";
          digits &= 0x3f;
        }
        format.m_digits=digits;
        f << "digits=" << digits << ",";
      }
    }
    else
      f << "##";
    if (val)
      f << "fl3=" << std::hex << val << std::dec << ",";
    if (!ok) {
      ascFile.addPos(pos-2);
      ascFile.addNote(f.str().c_str());
      return true;
    }
  }
  if (hasFont) {
    if (input->tell()+4>endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: problem reading font format\n"));
      f << "##font,";
      ascFile.addPos(pos-2);
      ascFile.addNote(f.str().c_str());
      return true;
    }
    MWAWFont font;
    auto size=static_cast<int>(input->readULong(1));
    auto flag = static_cast<int>(input->readULong(1));
    uint32_t flags=0;
    if (flag&0x1) flags |= MWAWFont::boldBit;
    if (flag&0x2) flags |= MWAWFont::italicBit;
    if (flag&0x4) font.setUnderlineStyle(MWAWFont::Line::Simple);
    if (flag&0x8) flags |= MWAWFont::embossBit;
    if (flag&0x10) flags |= MWAWFont::shadowBit;
    if (flag&0x20) font.setDeltaLetterSpacing(-1);
    if (flag&0x40) font.setDeltaLetterSpacing(1);
    if (flag&0x80) font.set(MWAWFont::Script::super100());
    if (size&0x80) {
      font.set(MWAWFont::Script::sub100());
      size&=0x7f;
    }
    font.setSize(float(size));
    font.setFlags(flags);
    font.setId(m_mainParser->getFontId(static_cast<int>(input->readULong(2))));
    cell.setFont(font);
    f << "font=[" << font.getDebugString(m_parserState->m_fontConverter) << "],";
  }
  MWAWCellContent &content=cell.m_content;
  if (hasPreFormula) {
    std::string extra("");
    auto &formula=content.m_formula;
    bool ok=readFormulaV2(cell.position(), formula, endPos, extra);
    f << "formula=[";
    for (auto const &form : formula)
      f << form;
    f << "]";
    if (!extra.empty()) f << ":" << extra;
    f << ",";
    if (!ok) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: can not read a preFormula\n"));
      f << "###formula,";
      ascFile.addPos(pos-2);
      ascFile.addNote(f.str().c_str());
      return true;
    }
    if (cell.validateFormula())
      content.m_contentType=MWAWCellContent::C_FORMULA;
    if (input->tell()!=endPos) ascFile.addDelimiter(input->tell(),'|');
  }
  val= hasAlign ? static_cast<int>(input->readULong(1)) : 0;
  int align= val&7;
  switch (align) {
  case 0:
    break;
  case 2:
    cell.setHAlignment(MWAWCell::HALIGN_LEFT);
    f << "left,";
    break;
  case 3:
    cell.setHAlignment(MWAWCell::HALIGN_CENTER);
    f << "center,";
    break;
  case 4:
    cell.setHAlignment(MWAWCell::HALIGN_RIGHT);
    f << "right,";
    break;
  case 5: // full(repeat)
    cell.setHAlignment(MWAWCell::HALIGN_LEFT);
    f << "repeat,";
    break;
  default:
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: find unknown alignment\n"));
    f << "##align=" << align << ",";
    break;
  }
  val&=0xF8;
  if (val) f << "align[high]=" << std::hex << val << std::dec << ",";
  if (hasFormula) {
    std::string extra("");
    std::vector<MWAWCellContent::FormulaInstruction> condition;
    auto &formula=hasPreFormula ? condition : content.m_formula;
    bool ok=readFormulaV2(cell.position(), formula, endPos, extra);
    if (hasPreFormula)
      f << "condition=[";
    else
      f << "formula=[";
    for (auto const &form : formula)
      f << form;
    f << "]";
    if (!extra.empty()) f << ":" << extra;
    f << ",";
    if (!ok) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: can not read a formula\n"));
      f << "###formula,";
      ascFile.addPos(pos-2);
      ascFile.addNote(f.str().c_str());
      return true;
    }
    if (!hasPreFormula && cell.validateFormula())
      content.m_contentType=MWAWCellContent::C_FORMULA;
    if (input->tell()!=endPos) ascFile.addDelimiter(input->tell(),'|');
  }

  actPos=input->tell();
  switch (type) {
  case 0:
    if (actPos!=endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: something look bad\n"));
      f << "###data";
      break;
    }
    break;
  case 3: {
    if (actPos+10!=endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: can not read a number\n"));
      f << "###number";
      break;
    }
    if (format.m_format==MWAWCell::F_UNKNOWN)
      format.m_format=MWAWCell::F_NUMBER;
    if (content.m_contentType!=MWAWCellContent::C_FORMULA)
      content.m_contentType=MWAWCellContent::C_NUMBER;
    double res;
    bool isNan;
    if (!input->readDouble10(res, isNan))
      f << "#value,";
    else {
      content.setValue(res);
      f << res << ",";
    }
    break;
  }
  case 7: {
    if (actPos+4!=endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: can not read a date\n"));
      f << "###number";
      break;
    }
    if (format.m_format==MWAWCell::F_UNKNOWN)
      format.m_format=MWAWCell::F_DATE;
    if (content.m_contentType!=MWAWCellContent::C_FORMULA)
      content.m_contentType=MWAWCellContent::C_NUMBER;
    auto Y=static_cast<int>(input->readULong(2));
    auto M=static_cast<int>(input->readULong(1));
    auto D=static_cast<int>(input->readULong(1));
    f << M << "/" << D << "/" << Y << ",";
    double res;
    if (!MWAWCellContent::date2Double(Y,M,D,res))
      f << "#date,";
    else
      content.setValue(res);
    break;
  }
  case 9: {
    auto sSz=static_cast<int>(input->readULong(1));
    if (actPos+1+sSz!=endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: can not read a text\n"));
      f << "###text";
      break;
    }
    if (format.m_format==MWAWCell::F_UNKNOWN)
      format.m_format=MWAWCell::F_TEXT;
    if (content.m_contentType!=MWAWCellContent::C_FORMULA)
      content.m_contentType=MWAWCellContent::C_TEXT;
    content.m_textEntry.setBegin(input->tell());
    content.m_textEntry.setLength(sSz);
    std::string text("");
    for (int i=0; i<sSz; ++i) text+=char(input->readULong(1));
    f << text << ",";
    break;
  }
  case 11: {
    if (actPos+1!=endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetCellV2: can not read a nan number\n"));
      f << "###nan";
      break;
    }
    if (format.m_format==MWAWCell::F_UNKNOWN)
      format.m_format=MWAWCell::F_NUMBER;
    if (content.m_contentType!=MWAWCellContent::C_FORMULA)
      content.m_contentType=MWAWCellContent::C_NUMBER;
    cell.m_content.setValue(std::numeric_limits<double>::quiet_NaN());
    val=static_cast<int>(input->readULong(1));
    f << "nan=" << val << ",";
    break;
  }
  default:
    break;
  }
  cell.setFormat(format);
  actPos=input->tell();
  if (actPos!=endPos)
    ascFile.addDelimiter(actPos,'|');
  ascFile.addPos(pos-2);
  ascFile.addNote(f.str().c_str());
  return true;
}

bool RagTimeSpreadsheet::readSpreadsheetExtraV2(MWAWEntry &entry, RagTimeSpreadsheetInternal::Spreadsheet &sheet)
{
  MWAWInputStreamPtr input = m_parserState->m_input;
  long pos=entry.begin();
  long endPos=entry.end();
  if (pos<=0 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetExtraV2: the position seems bad\n"));
    return false;
  }

  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  ascFile.addPos(endPos);
  ascFile.addNote("_");

  for (int i=0; i<2; ++i) {
    pos=input->tell();
    f.str("");
    static char const *what[]= {"SpreadsheetRow", "SpreadsheetCol"};
    f << "Entries(" << what[i] << "):";
    auto n=static_cast<int>(input->readULong(2));
    f << "N=" << n << ",";
    static int const dataSize[]= {20,14};
    if (pos+2+dataSize[i]*n>endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetExtraV2: problem reading some spreadsheetZone Col/Row field\n"));
      f << "###";
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      return false;
    }
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    int prevDim=0;
    std::vector<float> &dims=i==0 ? sheet.m_heightRows : sheet.m_widthCols;
    for (int j=0; j<n; ++j) {
      pos=input->tell();
      f.str("");
      f << what[i] << "-" << j << ":";
      int val;
      for (int k=0; k<7; ++k) { // f0=0|80, f1=0|8|10, f4=0|20|60|61, f5=2|42|82,f6=1|3
        val=static_cast<int>(input->readULong(1));
        if (val) f << "f" << k << "=" << std::hex << val << std::dec << ",";
      }
      f << "font[";
      f << "sz=" << input->readLong(2) << ",";
      val=static_cast<int>(input->readULong(1));
      if (val) f << "fl=" << std::hex << val << std::dec << ",";
      f << "id=" << input->readULong(2) << ",";
      f << "],";
      auto dim=static_cast<int>(input->readULong(2));
      if (dim<prevDim) {
        f << "###dim, ";
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readSpreadsheetExtraV2: problem reading some position\n"));
        dims.push_back(0);
      }
      else {
        dims.push_back(float(dim-prevDim));
        prevDim=dim;
      }
      f << "dim=" << float(dim-prevDim) << ",";
      if (i==0) {
        f << "height=" << input->readULong(2) << ",";
        long rowPos=sheet.m_cellsBegin+long(input->readULong(4));
        sheet.m_rowPositionsList.push_back(rowPos);
        f << "pos?=" << std::hex << rowPos << std::dec << ",";
      }
      input->seek(pos+dataSize[i], librevenge::RVNG_SEEK_SET);
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
    }
  }

  /* finally something like
     86000000200201000c000014003c00030c090000
     or
     86000000204201000a000003004000060d0a000000000ce5410000010000000000000000688f688f688f0000000000000000688f688f688f000000000000
     font + ?
  */
  ascFile.addPos(input->tell());
  ascFile.addNote("SpreadsheetZone[end]:");
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return true;
}

////////////////////////////////////////////////////////////
// read a zone of spreadsheet
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
//
// Low level
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// send data to the listener
////////////////////////////////////////////////////////////
bool RagTimeSpreadsheet::send(RagTimeSpreadsheetInternal::Spreadsheet &sheet, MWAWSpreadsheetListenerPtr listener)
{
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::send: I can not find the listener\n"));
    return false;
  }
  sheet.m_isSent=true;
  std::vector<float> colWidth=sheet.getColumnsWidth();
  listener->openSheet(colWidth, librevenge::RVNG_POINT, std::vector<int>(), sheet.m_name);

  MWAWInputStreamPtr &input=m_parserState->m_input;
  int prevRow = -1;
  float rowHeight=0;
  for (auto cIt : sheet.m_cellsMap) {
    RagTimeSpreadsheetInternal::Cell cell= cIt.second;
    if (cell.position()[1]>prevRow+1) {
      while (cell.position()[1] > prevRow+1) {
        if (prevRow != -1) listener->closeSheetRow();
        int numRepeat;
        float h=sheet.getRowHeight(prevRow+1, numRepeat);
        if (cell.position()[1]<prevRow+1+numRepeat)
          numRepeat=cell.position()[1]-1-prevRow;
        listener->openSheetRow(h, librevenge::RVNG_POINT, numRepeat);
        prevRow+=numRepeat;
      }
    }
    if (cell.position()[1] > prevRow) {
      if (prevRow != -1) listener->closeSheetRow();
      listener->openSheetRow(sheet.getRowHeight(++prevRow), librevenge::RVNG_POINT);
    }
    MWAWCellContent content=cell.m_content;
    // change the reference date from 1/1/1904 to 1/1/1900
    if (cell.getFormat().m_format==MWAWCell::F_DATE && content.isValueSet())
      content.setValue(content.m_value+1460.);
    listener->openSheetCell(cell, content);
    if (cell.m_textEntry.valid()) {
      listener->setFont(cell.getFont());
      int width=0;
      if ((cell.m_rotation>45 && cell.m_rotation<145)||
          (cell.m_rotation>225 && cell.m_rotation<315))
        width=int(rowHeight);
      else if (cell.position()[0]>=0 || cell.position()[0]<int(colWidth.size()))
        width=int(colWidth[size_t(cell.position()[0])]);
      else {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::send: can not determine the text width zone\n"));
      }
      if (m_mainParser->readTextZone(cell.m_textEntry, width))
        m_mainParser->sendText(cell.m_textEntry.id(), listener);
      else {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::send: can not find a text zone\n"));
      }
    }
    else if (cell.m_content.m_textEntry.valid()) {
      listener->setFont(cell.getFont());
      input->seek(cell.m_content.m_textEntry.begin(), librevenge::RVNG_SEEK_SET);
      while (!input->isEnd() && input->tell()<cell.m_content.m_textEntry.end()) {
        auto c=static_cast<unsigned char>(input->readULong(1));
        if (c==0xd)
          listener->insertEOL();
        else
          listener->insertCharacter(c);
      }
    }
    listener->closeSheetCell();
  }
  if (prevRow!=-1) listener->closeSheetRow();
  listener->closeSheet();
  return true;
}

bool RagTimeSpreadsheet::send(int zId, MWAWPosition const &pos)
{
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::send: I can not find the listener\n"));
    return false;
  }
  auto it=m_state->m_idSpreadsheetMap.find(zId);
  if (it==m_state->m_idSpreadsheetMap.end() || !it->second) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::send: can not find the spreadsheet %d\n", zId));
    return false;
  }
  auto &sheet=*it->second;
  MWAWBox2f box=MWAWBox2f(MWAWVec2f(0,0), pos.size());
  MWAWSpreadsheetEncoder spreadsheetEncoder;
  MWAWSpreadsheetListenerPtr spreadsheetListener(new MWAWSpreadsheetListener(*m_parserState, box, &spreadsheetEncoder));
  spreadsheetListener->startDocument();
  send(sheet, spreadsheetListener);
  spreadsheetListener->endDocument();
  MWAWEmbeddedObject object;
  if (spreadsheetEncoder.getBinaryResult(object))
    listener->insertPicture(pos, object);
  return true;
}

void RagTimeSpreadsheet::flushExtra()
{
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::flushExtra: can not find the listener\n"));
    return;
  }
  for (auto it : m_state->m_idSpreadsheetMap) {
    if (!it.second) continue;
    auto const &zone=*it.second;
    if (zone.m_isSent) continue;
    static bool first=true;
    if (first) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::flushExtra: find some unsend zone\n"));
      first=false;
    }
    MWAWPosition pos(MWAWVec2f(0,0), MWAWVec2f(200,200), librevenge::RVNG_POINT);
    pos.m_anchorTo=MWAWPosition::Char;
    send(it.first, pos);
    listener->insertEOL();
  }
}

////////////////////////////////////////////////////////////
// formula
////////////////////////////////////////////////////////////
bool RagTimeSpreadsheet::readCellInFormula(MWAWVec2i const &cellPos, bool canBeList, MWAWCellContent::FormulaInstruction &instr, long endPos, std::string &extra)
{
  bool isList=canBeList;
  MWAWInputStreamPtr input=m_parserState->m_input;
  libmwaw::DebugStream f;

  instr.m_type= isList ? MWAWCellContent::FormulaInstruction::F_CellList :
                MWAWCellContent::FormulaInstruction::F_Cell;
  f << "Cell=";
  long pos=input->tell();
  if (pos+2>endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: the size seems bad\n"));
    f << "#size,";
    extra=f.str();
    return false;
  }
  auto val=static_cast<int>(input->readULong(1));
  if (val&0x30) { // does not seem to have any sens
    f << "fl=" << std::hex << (val&0x30) << std::dec << ",";
    val &= 0xCF;
  }
  if (isList) {
    if ((val&0x40)==0)
      f << "noFlList,";
    val &= 0xBF;
  }
  if (val<0x80 || (!isList && val>0x83) || (isList && val>0x8f))  {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: find unknown begin\n"));
    f << "#f0=" << std::hex << val << std::dec << ",";
    extra=f.str();
    return false;
  }
  instr.m_positionRelative[0][0]=(val&2);
  instr.m_positionRelative[0][1]=(val&1);
  instr.m_positionRelative[1][0]=(val&8);
  instr.m_positionRelative[1][1]=(val&4);
  val=static_cast<int>(input->readULong(1));
  int type0=(val>>5)&7;
  int type1=(val>>2)&7;
  if (type0&4) {
    type0&=3;
    instr.m_type=MWAWCellContent::FormulaInstruction::F_CellList;
    isList=true;
  }
  else { // no cell 0, ie a simple cell list reduced to a cell
    instr.m_type=MWAWCellContent::FormulaInstruction::F_Cell;
    isList=false;
  }
  for (int c=0; c<2; ++c) {
    switch (type0) {
    case 0:
      if (instr.m_positionRelative[c][1]==false) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: find unexpected type0 cell\n"));
        f << "#type0[abs],";
        extra=f.str();
        return false;
      }
      instr.m_position[c][1]=cellPos[1];
      if (instr.m_positionRelative[c][0]==true) {
        if (val<0x10)
          instr.m_position[c][0]=cellPos[0]+val;
        else if (val<0x20)
          instr.m_position[c][0]=cellPos[0]+val-0x20;
      }
      else
        instr.m_position[c][0]=val-5;
      break;
    case 1:
      if (instr.m_positionRelative[c][0]==false) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: find unexpected type0 cell\n"));
        f << "#type0=1[abs],";
        extra=f.str();
        return false;
      }
      val &= 0x1F;
      instr.m_position[c][0]=cellPos[0];
      if (instr.m_positionRelative[c][1]==true) {
        if (val<0x10)
          instr.m_position[c][1]=cellPos[1]+val;
        else if (val<0x20)
          instr.m_position[c][1]=cellPos[1]+val-0x20;
      }
      else
        instr.m_position[c][1]=val-5;
      break;
    case 2: {
      int firstValue=((val>>2)&0x7);
      int secondValue=(val&0x3);
      if (instr.m_positionRelative[c][0]==true) {
        if (instr.m_positionRelative[c][1]==false) {
          if (firstValue==0) {
            MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: can not determine the row\n"));
            f << "#type0=2[row],";
            extra=f.str();
            return false;
          }
          instr.m_position[c][1]=firstValue-1;
        }
        else if (firstValue&4)
          instr.m_position[c][1]=cellPos[1]+firstValue-8;
        else
          instr.m_position[c][1]=cellPos[1]+firstValue;
        if (secondValue&2)
          instr.m_position[c][0]=cellPos[0]+secondValue-4;
        else
          instr.m_position[c][0]=cellPos[0]+secondValue;
      }
      else if (instr.m_positionRelative[c][1]==true) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: find unexpected type0 cell\n"));
        f << "#type0=2[rel/abs],";
        extra=f.str();
        return false;
      }
      else {
        std::stringstream s;
        s << "Ref" << firstValue << ",";
        instr.m_sheet=s.str();
        return true;
      }
      break;
    }
    case 3: {
      if (type1>5) {
        f << "type0=" << type0 << ",type1=" << type1 << ",";
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: unexpected number of bytes for type1\n"));
        extra=f.str();
        return false;
      }
      int nExpectedBits=2*type1+3+(type1>=4?3:0); // between 3 and 16
      int nBytesToRead=(nExpectedBits-2+7)/8; // between 1 and 2
      if (input->tell()+nBytesToRead>endPos) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: unexpected end\n"));
        f << "#end[row],";
        extra=f.str();
        return false;
      }
      int readValue=((val&3)<<(nBytesToRead*8))+static_cast<int>(input->readULong(nBytesToRead));
      int nBitRemain=2+8*nBytesToRead-nExpectedBits;
      int secondValue=(readValue&((1<<nBitRemain)-1));

      int firstValue=(readValue>>nBitRemain);
      if (instr.m_positionRelative[c][1]==true) {
        if (firstValue>=(1<<(nExpectedBits-1)))
          firstValue-=(1<<nExpectedBits);
        firstValue+=cellPos[1];
      }
      else
        --firstValue;
      instr.m_position[c][1]=firstValue;

      if ((instr.m_positionRelative[c][0]==false && secondValue==0) ||
          (instr.m_positionRelative[c][0]==true && nBitRemain<=2)) {
        if (input->tell()>=endPos) {
          MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: unexpected end\n"));
          f << "#end[col],";
          extra=f.str();
          return false;
        }
        secondValue=static_cast<int>(input->readULong(1));
        nBitRemain=8;
      }
      if (instr.m_positionRelative[c][0]==false) {
        if (secondValue<5) {
          std::stringstream s;
          s << "Ref" << firstValue+1 << ",";
          instr.m_position[c][1]=0;
          instr.m_sheet=s.str();
          return true;
        }
        else
          instr.m_position[c][0]=secondValue-5;
      }
      // checkme: seems to work, but I am very unsure of this code
      else if (nBitRemain!=8 && (secondValue&(1<<(nBitRemain-1))))
        instr.m_position[c][0]=(256+secondValue+cellPos[0]-(1<<nBitRemain))%256;
      else
        instr.m_position[c][0]=(secondValue+cellPos[0])%256;
      break;
    }
    default:
      f << "type0=" << type0 << ",type1=" << type1 << ",";
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: I only know how to read type0 field\n"));
      extra=f.str();
      return false;
    }

    if (!isList || c==1) break;
    if (input->tell()>=endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: can not read the second cell\n"));
      f << "#end[cell]=" << instr.m_position[0] << ",";
      extra=f.str();
      return false;
    }

    val=static_cast<int>(input->readULong(1));
    type0=(val>>5)&7;
    type1=(val>>2)&7;
  }
  if (instr.m_position[0][0]<0||instr.m_position[0][1]<0||
      (instr.m_type==MWAWCellContent::FormulaInstruction::F_CellList &&
       (instr.m_position[1][0]<0||instr.m_position[1][1]<0))) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormula: bads cell\n"));
    f << "#cell=" << instr << ",";
    extra=f.str();
    return false;
  }
  return true;
}

bool RagTimeSpreadsheet::readCellInFormulaV2(MWAWVec2i const &cellPos, bool canBeList, MWAWCellContent::FormulaInstruction &instr, long endPos, std::string &extra)
{
  MWAWInputStreamPtr input=m_parserState->m_input;
  libmwaw::DebugStream f;

  instr.m_type=MWAWCellContent::FormulaInstruction::F_Cell;
  f << "Cell=";
  bool ok=true;
  int which=0;
  int page=-1, frame=-1;
  while (!input->isEnd()) {
    long pos=input->tell();
    if (pos+2>endPos) break;
    auto what=static_cast<int>(input->readULong(1));
    if (canBeList && (what==0x83 || what==0x84) && which==0) {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_CellList;
      which=1;
      what&=0xF;
      instr.m_position[1]=instr.m_position[0];
      instr.m_positionRelative[1]=instr.m_positionRelative[0];
    }
    if (what < 3 || what > 6) {
      ok=false;
      f << "##marker=" << what << ",";
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: find unknown marker %d\n", what));
      break;
    }
    auto val = static_cast<int>(input->readULong(1));
    int flag=0;
    if (val==0x80 || val==0xc0 || val==0xFF) {
      flag=val;
      val = static_cast<int>(input->readULong(1));
    }
    if (input->tell()>endPos) {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: the %d marker data seems odd\n", what));
      f << "###market[data]=" << what << ",";
      ok=false;
      break;
    }
    bool absolute=false;
    if ((flag==0&&!(val&0xC0)) || (flag==0x80 && (val&0xC0)))
      absolute=true;
    else if (flag==0&&(val&0xe0)==0x60) {
      if (what>=5) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: find relative position for sheet\n"));
        f << "###";
      }
      else
        val += 1-0x80+cellPos[4-what];
    }
    else if (flag==0&&(val&0xe0)==0x40) {
      if (what>=5) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: find relative position for sheet\n"));
        f << "###";
      }
      else
        val += 1-0x40+cellPos[4-what];
    }
    else if (flag==0xc0) {
      if (what>=5) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: find relative position for sheet\n"));
        f << "###";
      }
      else
        val += 1+cellPos[4-what];
    }
    else if (flag==0xff) {
      if (what>=5) {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: find relative position for sheet\n"));
        f << "###";
      }
      else
        val += -0xff+cellPos[4-what];
    }
    else {
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: can not read a cell position position for sheet\n"));
      f << "###v" << what << "=" << std::hex << val << "[" << flag << "]" << std::dec;
      ok=false;
      break;
    }
    if (what==3||what==4) {
      instr.m_position[which][4-what]=(val-1);
      instr.m_positionRelative[which][4-what]=!absolute;
    }
    else if (what==5 || what==6) {
      if (what==5) frame=val;
      else if (what==6) page=val;
      std::stringstream s;
      s << "Sheet";
      if (page>=0) s << "P" << page;
      if (frame>=0) s << "F" << frame;
      instr.m_sheet=s.str();
    }
    // coverity[dead_error_line : FALSE]: intended as sanity check
    else {
      f << "##marker=" << what << ",";
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: find unexpected marker %d\n", what));
      break;
    }
  }
  if (ok && input->tell()!=endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: find extra data at the end of a cell\n"));
    f << "###cell[extra],";
    ok=false;
  }
  if (ok && (instr.m_position[0][0]<0||instr.m_position[0][0]>255 ||
             instr.m_position[0][1]<0||instr.m_position[0][1]>255)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: something go wrong\n"));
    f << "###cell[position],";
    ok=false;
  }
  if (ok && instr.m_type==MWAWCellContent::FormulaInstruction::F_CellList &&
      (instr.m_position[1][0]<0||instr.m_position[1][0]>255 ||
       instr.m_position[1][1]<0||instr.m_position[1][1]>255)) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readCellInFormulaV2: something go wrong\n"));
    f << "###cell[position2],";
    ok=false;
  }
  extra=f.str();
  return ok;
}

bool RagTimeSpreadsheet::readFormula(MWAWVec2i const &cellPos, std::vector<MWAWCellContent::FormulaInstruction> &formula, long endPos, std::string &extra)
{
  formula.resize(0);

  MWAWInputStreamPtr input=m_parserState->m_input;
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  bool ok=true, lastIsClosePara=false;
  while (!input->isEnd()) {
    long pos=input->tell();
    if (pos >= endPos)
      break;
    int val, type=static_cast<int>(input->readULong(1));
    MWAWCellContent::FormulaInstruction instr;
    if (type==0 && pos+1==endPos) // rare but seems ok
      break;
    switch (type) {
    case 5:
      if (pos+1+2 > endPos) {
        ok = false;
        break;
      }
      val = static_cast<int>(input->readLong(2));
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Long;
      instr.m_longValue=val;
      break;
    case 7:
    case 8: { // date
      if (pos+1+10 > endPos) {
        ok = false;
        break;
      }
      double value;
      bool isNan;
      input->readDouble10(value, isNan);
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Double;
      instr.m_doubleValue=value;
      break;
    }
    case 9: {
      auto sSz=static_cast<int>(input->readULong(1));
      if (pos+1+2+sSz > endPos) {
        ok = false;
        break;
      }
      val=char(input->readULong(1));
      if (val!='"' && val!='\'') {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readFormula: the first string char seems odd\n"));
        f << "##fChar=" << val << ",";
      }
      std::string text("");
      for (int i=0; i<sSz; ++i) text+=char(input->readULong(1));
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Text;
      instr.m_content=text;
      break;
    }
    case 0xf: // if delimiter with unknown value
      if (pos+1+1 > endPos) {
        ok = false;
        break;
      }
      val = static_cast<int>(input->readLong(1));
      f << "unkn[sep]=" << val << ",";
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content=";";
      break;
    case 0x32: {
      if (pos+1+1 > endPos) {
        ok = false;
        break;
      }
      val=static_cast<int>(input->readULong(1));
      std::string funct("");
      if (val>=0 && val<0x60) {
        static char const *s_functions[] = {
          // 0
          "Abs(", "Sign(", "Rand()", "Sqrt(", "Sum(", "SumSq(", "Max(", "Min(",
          "Average(", "StDev(", "Pi()", "Sin(", "ASin(", "Cos(", "ACos(", "Tan(",
          // 1
          "ATan(", "Exp(", "Exp1(" /* fixme: exp(x+1)*/, "Ln(", "Ln1(" /* fixme: ln(n+1) */, "Log10(", "Annuity(", "Rate(",
          "PV(", "If(", "True()", "False()", "Len(", "Mid(", "Rept(", "Int(",
          // 2
          "Round(", "Text("/* add a format*/, "Dollar(", "Value(", "Number("/* of cell in list*/, "Row()", "Column()", "Index(",
          "Find(", "", "Page()", "Frame()" /* frame?*/, "IsError(", "IsNA(","NA()", "Day(",
          // 3
          "Month(", "Year("/* or diffyear*/, "DayOfYear("/* checkme*/, "SetDay(", "SetMonth(", "SetYear(",
          "AddMonth(", "AddYear(", "Today()", "PrintCyle()"/*print ?*/, "PrintStop("/*print stop*/, "Choose("/*checkme or select */, "Type(", "Find(", "SetFileName(", "Today()"/*hour*/,
          // 4
          "Today()"/*Minute*/, "", "Second(", "Minute(", "Hour(", "DayOfWeek(", "WeekOf(", "Slope(",
          "Intersect(", "LogSlope(", "LogIntersect(", "Mailing(", "Cell(", "Button(", "Today()"/*Now*/, "TotalPage()",

          // 5
          "", "DayOfWeekUS(", "WeekOfUS(", "", "", "", "", "",
          "", "", "", "", "", "", "", "",
        };
        funct=s_functions[val];
      }
      if (funct.empty()) {
        std::stringstream s;
        s << "Funct" << std::hex << val << std::dec << "(";
        funct=s.str();
      }
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
      instr.m_content=funct;
      size_t functionLength=funct.length();
      if (functionLength>2 && instr.m_content[functionLength-1]==')') {
        instr.m_content.resize(functionLength-2);
        formula.push_back(instr);
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
        instr.m_content="(";
        formula.push_back(instr);
        instr.m_content=")";
      }
      else if (functionLength>1 && instr.m_content[functionLength-1]=='(') {
        instr.m_content.resize(functionLength-1);
        formula.push_back(instr);
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
        instr.m_content="(";
      }

      break;
    }
    default: {
      if ((type&0xCC)==0x80) { // checkme: maybe (type&0xC0)==0x80
        std::string error("");
        input->seek(-1, librevenge::RVNG_SEEK_CUR);
        ok=readCellInFormula(cellPos, false, instr, endPos, error);
        if (!ok) f << "cell=[" << error << "],";
        break;
      }
      if ((type&0x80)==0x80) {
        std::string error("");
        input->seek(-1, librevenge::RVNG_SEEK_CUR);
        ok=readCellInFormula(cellPos, true, instr, endPos, error);
        if (!ok) f << "cell[list]=[" << error << "],";
        break;
      }
      static char const *s_operators[] = {
        // 0
        "", "", "", "", "", "", "", "",
        "", "", "", "", "(", ")", ";", "",
        // 1
        /* fixme: we need to reconstruct the formula for operator or, and, ... */        "", "", "^", "", "", "*", "/", "",
        "And", "", "", "", "", "+", "-", "Or",
        // 2
        "&", "", "", "", "=", "<", ">", "<=",
        ">=", "<>", "", "", "", "", "", "",
      };
      std::string op("");
      if (type>=0 && type< 0x30)
        op=s_operators[type];
      if (op.empty()) {
        ok=false;
        break;
      }
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content=op;
      break;
    }
    }
    if (!ok) {
      ascFile.addDelimiter(pos,'#');
      f << "###";
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readFormula: can not read a formula\n"));
      break;
    }
    // a cell ref followed Today(), Page(), ... so we must ignored it
    if (lastIsClosePara && (instr.m_type==MWAWCellContent::FormulaInstruction::F_Cell ||
                            instr.m_type==MWAWCellContent::FormulaInstruction::F_CellList)) {
      lastIsClosePara=false;
      continue;
    }
    lastIsClosePara= instr.m_type==MWAWCellContent::FormulaInstruction::F_Operator &&
                     instr.m_content==")";
    formula.push_back(instr);
  }
  extra=f.str();
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  if (ok) return true;

  f.str("");
  for (auto const &form : formula)
    f << form;
  f << "," << extra;
  extra=f.str();
  formula.resize(0);
  return true;
}

bool RagTimeSpreadsheet::readFormulaV2(MWAWVec2i const &cellPos, std::vector<MWAWCellContent::FormulaInstruction> &formula, long endPos, std::string &extra)
{
  formula.resize(0);

  MWAWInputStreamPtr input=m_parserState->m_input;
  long pos=input->tell();
  libmwaw::DebugFile &ascFile=m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  long formulaEndPos=pos+1+long(input->readULong(1));
  if (formulaEndPos>endPos) {
    MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readFormulaV2: the formula size seems bad\n"));
    return false;
  }
  ascFile.addDelimiter(pos,'|');
  bool ok=true;
  while (!input->isEnd()) {
    pos=input->tell();
    if (pos >= formulaEndPos)
      break;
    int val, type=static_cast<int>(input->readULong(1));
    MWAWCellContent::FormulaInstruction instr;
    switch (type) {
    case 5:
      if (pos+1+2 > formulaEndPos) {
        ok = false;
        break;
      }
      val = static_cast<int>(input->readLong(2));
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Long;
      instr.m_longValue=val;
      break;
    case 6: {
      if (pos+1+10 > formulaEndPos) {
        ok = false;
        break;
      }
      double value;
      bool isNan;
      input->readDouble10(value, isNan);
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Double;
      instr.m_doubleValue=value;
      break;
    }
    case 8: {
      if (pos+1+4 > formulaEndPos) {
        ok = false;
        break;
      }
      auto Y=static_cast<int>(input->readULong(2));
      auto M=static_cast<int>(input->readULong(1));
      auto D=static_cast<int>(input->readULong(1));
      double value;
      if (!MWAWCellContent::date2Double(Y,M,D,value)) {
        f << "#date,";
        break;
      }
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Double;
      instr.m_doubleValue=value;
      break;
    }
    case 9: {
      auto sSz=static_cast<int>(input->readULong(1));
      if (pos+1+2+sSz > formulaEndPos) {
        ok = false;
        break;
      }
      val=char(input->readULong(1));
      if (val!='"' && val!='\'') {
        MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readFormulaV2: the first string char seems odd\n"));
        f << "##fChar=" << val << ",";
      }
      std::string text("");
      for (int i=0; i<sSz; ++i) text+=char(input->readULong(1));
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Text;
      instr.m_content=text;
      break;
    }
    case 0xf: // if delimiter with unknown value
      if (pos+1+1 > formulaEndPos) {
        ok = false;
        break;
      }
      val = static_cast<int>(input->readLong(1));
      f << "unkn[sep]=" << val << ",";
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content=";";
      break;
    default: {
      if ((type&0xF0)==0x80) {
        long endCellPos=pos+1+(type-0x80);
        if (endCellPos > formulaEndPos) {
          ok = false;
          break;
        }
        std::string error("");
        ok=readCellInFormulaV2(cellPos, false, instr, endCellPos, error);
        if (!ok) f << "cell=[" << error << "],";
        break;
      }
      if ((type&0xF0)==0xc0 || (type&0xF0)==0xd0) {
        long endCellPos=pos+1+(type-0xc0);
        if (endCellPos > formulaEndPos) {
          ok = false;
          break;
        }
        std::string error("");
        ok=readCellInFormulaV2(cellPos, true, instr, endCellPos, error);
        if (!ok) f << "cell=[" << error << "],";
        break;
      }
      static char const *s_functions[] = {
        // 0
        "", "", "", "", "", "", "", "",
        "", "", "", "", "(", ")", ";", "",
        // 1
        /* fixme: we need to reconstruct the formula for operator or, and, ... */
        "", "", "^", "", "", "*", "/", "",
        "And", "", "", "", "", "+", "-", "Or",
        // 2
        "", "", "", "", "=", "<", ">", "<=",
        ">=", "<>", "", "", "", "", "", "",
        // 3
        "", "", "Abs(", "Sign(", "Rand()", "Sqrt(", "Sum(", "SumSq(",
        "Max(", "Min(", "Average(", "StDev(", "Pi()", "Sin(", "ASin(", "Cos(",
        // 4
        "ACos(", "Tan(", "ATan(", "Exp(", "Exp1(" /* fixme: exp(x+1)*/, "Ln(", "Ln1(" /* fixme: ln(n+1) */, "Log10(",
        "Annuity(", "Rate(", "PV(", "If(", "True()", "False()", "Len(", "Mid(",
        // 5
        "Rept(", "Int(", "Round(", "Text("/* add a format*/, "Dollar(", "Value(", "Number(", "Row()",
        "Column()", "Index(", "Find(", "", "Page()", "Frame()" /* frame?*/, "IsError(", "IsNA(",
        // 6
        "NA()", "Day(", "Month(", "Year(", "DayOfYear("/* checkme*/, "SetDay(", "SetMonth(", "SetYear(",
        "AddMonth(", "AddYear(", "Today()", "Funct6b()"/*print ?*/, "Funct6c("/*print stop*/, "Choose("/*checkme or select */, "Type(", "Find(",
        // 70
        "SetFileName(", "", "", "", "", "", "", "",
        "", "", "", "", "", "", "", "",
      };
      if (type>=0 && type < 0x80)
        instr.m_content=s_functions[type];
      size_t functionLength=instr.m_content.length();
      if (functionLength==0) {
        ok=false;
        break;
      }
      if (instr.m_content[0] >= 'A' && instr.m_content[0] <= 'Z') {
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
        if (functionLength>2 && instr.m_content[functionLength-1]==')') {
          instr.m_content.resize(functionLength-2);
          formula.push_back(instr);
          instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
          instr.m_content="(";
          formula.push_back(instr);
          instr.m_content=")";
        }
        else if (instr.m_content[functionLength-1]=='(') {
          instr.m_content.resize(functionLength-1);
          formula.push_back(instr);
          instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
          instr.m_content="(";
        }
      }
      else
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      break;
    }
    }
    if (!ok) {
      ascFile.addDelimiter(pos,'#');
      f << "###";
      MWAW_DEBUG_MSG(("RagTimeSpreadsheet::readFormulaV2: can not read a formula\n"));
      break;
    }
    formula.push_back(instr);
  }
  extra=f.str();
  input->seek(formulaEndPos, librevenge::RVNG_SEEK_SET);
  if (ok) return true;

  f.str("");
  for (auto const &form : formula)
    f << form;
  f << "," << extra;
  extra=f.str();
  formula.resize(0);
  return true;
}

// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: