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 <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <sstream>
#include <stack>

#include <librevenge/librevenge.h>

#include "MWAWCell.hxx"
#include "MWAWFont.hxx"
#include "MWAWGraphicEncoder.hxx"
#include "MWAWGraphicListener.hxx"
#include "MWAWListener.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSpreadsheetEncoder.hxx"
#include "MWAWSpreadsheetListener.hxx"
#include "MWAWSubDocument.hxx"

#include "RagTime5ClusterManager.hxx"
#include "RagTime5Parser.hxx"
#include "RagTime5StructManager.hxx"
#include "RagTime5StyleManager.hxx"

#include "RagTime5Spreadsheet.hxx"

#include "libmwaw_internal.hxx"

/** Internal: the structures of a RagTime5Spreadsheet */
namespace RagTime5SpreadsheetInternal
{

//! internal: a structure used to store a value in a cell in RagTime5SpreadsheetInternal
struct CellValue {
  //! constructor
  CellValue()
    : m_type(0)
    , m_id(0)
    , m_long(0)
    , m_double(0)
    , m_text("")
    , m_fieldId(0)
    , m_extra("")
  {
  }
  //! update the cell's content
  void update(MWAWCell &cell, MWAWCellContent &content) const
  {
    MWAWCell::Format format=cell.getFormat();
    switch (m_type) {
    case 4:
      format.m_format=MWAWCell::F_NUMBER;
      content.m_contentType=MWAWCellContent::C_NUMBER;
      content.setValue(m_double);
      break;
    case 5:
      format.m_format=MWAWCell::F_DATE;
      content.m_contentType=MWAWCellContent::C_NUMBER;
      content.setValue(m_double+1460);
      break;
    case 6:
      format.m_format=MWAWCell::F_TIME;
      content.m_contentType=MWAWCellContent::C_NUMBER;
      content.setValue(m_double);
      break;
    case 7:
      format.m_format=MWAWCell::F_TEXT;
      content.m_contentType=MWAWCellContent::C_TEXT;
      break;
    default:
      break;
    }
    cell.setFormat(format);
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, CellValue const &cell)
  {
    switch (cell.m_type) {
    case 0: // empty
      break;
    case 1: // long or some id
      o << "type1=" << cell.m_id << ",";
      break;
    case 2: // unknown
      o << "type2=" << std::hex << cell.m_long << std::dec << ",";
      break;
    case 4:
      o << "number=" << cell.m_double << ",";
      break;
    case 5:
      o << "date=" << cell.m_double << ",";
      break;
    case 6:
      o << "time=" << cell.m_double << ",";
      break;
    case 7:
      o << "text=\"" << cell.m_text.cstr() << "\",";
      break;
    case 8:
    case 9:
      o << (cell.m_type==8 ? "type8" : "zone[id]") << "="
        << (cell.m_id&0xFFFFFF) << ":" << (cell.m_id>>24) << ",";
      break;
    case 0xa:
      o << "pict[id]=" << cell.m_id << ",";
      break;
    default:
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::CellValue::operator<<: unknown type\n"));
      o << "##type=" << cell.m_type << ",";
    }
    if (cell.m_fieldId)
      o << "fieldDef-" << cell.m_fieldId << ",";
    o << cell.m_extra;
    return o;
  }
  //! the cell type
  int m_type;
  //! a id value
  unsigned long m_id;
  //! a long value
  long m_long;
  //! a double value
  double m_double;
  //! the text
  librevenge::RVNGString m_text;
  //! the field id
  int m_fieldId;
  //! extra data
  std::string m_extra;
};

//! a struct to store what a cell contains
struct CellContent {
  //! enum to define the id position
  enum IdPosition { Value=0, Union, GraphicStyle, TextStyle,
                    BorderPrevVStyle, BorderNextVStyle, BorderPrevHStyle, BorderNextHStyle
                  };

  //! constructor
  CellContent(MWAWVec2i const &pos, int plane)
    : m_position(pos)
    , m_plane(plane)
    , m_isMerged(false)
  {
    for (auto &id : m_id) id=-1;
  }
  //! returns true if the cell is merged
  bool isMergedCell() const
  {
    return m_isMerged;
  }
  //! sets the cell content
  void setContent(int id, int contentId)
  {
    if (id<0 || id>=8) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::CellContent: called with bad id=%d\n", id));
    }
    else
      m_id[id]=contentId;
  }
  //! returns true if the cell has no id or is merged
  bool hasContent() const
  {
    if (m_isMerged) return false;
    for (auto id : m_id) {
      if (id>=0)
        return true;
    }
    return false;
  }
  //! small operator<<
  friend std::ostream &operator<<(std::ostream &o, CellContent const &cell)
  {
    if (cell.m_isMerged) o << "_[" << cell.m_position << "]";
    else if (!cell.hasContent()) o << "*";
    else {
      o << "[";
      for (int i=0; i<8; ++i) {
        if (cell.m_id[i]<0) continue;
        static char const *wh[]= {"V", "U", "G", "T", "bv", "BV", "bh", "BH"};
        o << wh[i] << cell.m_id[i] << ",";
      }
      o << "]";
    }
    return o;
  }
  /** the cell position

   \note if the cell is an merged cell, this corresponds to the first cell*/
  MWAWVec2i m_position;
  //! the cell plane
  int m_plane;
  //! a flag to know if the cell is merged
  bool m_isMerged;
  //! the list of id
  int m_id[8];
};

//! a border style PLC
struct BorderPLC {
  //! constructor
  BorderPLC()
    : m_values(6,0)
  {
  }
  //! constructor
  explicit BorderPLC(std::vector<int> const &values) : m_values(values)
  {
    if (values.size()==6)
      return;
    MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::BorderPLC::BorderPLC: bad value size\n"));
    m_values.resize(6,0);
  }
  //! returns true if the cell is a merged cell
  bool isMergedBorder() const
  {
    return m_values.size()==6 && (m_values[5]&0x300)==0x300;
  }
  //! returns the graphic style border id corresponding to this cell
  int getBorderGraphicStyleId(bool prevCell) const
  {
    return m_values[prevCell ? 0 : 2];
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, BorderPLC const &plc)
  {
    for (size_t i=0; i<6; ++i) {
      int val=plc.m_values[i];
      if (!val) continue;
      switch (i) {
      case 0:
        o << "GS" << val << "[prevCell],";
        break;
      case 2:
        o << "GS" << val << "[nextCell],";
        break;
      case 5:
        if ((val&0x300)==0x300) {
          o << "none[merged],";
          val&=0xFCFF;
        }
        // find also 2
        if (val)
          o << "fl=" << std::hex << val << std::dec << ",";
        break;
      default: // always 0?
        o << "f" << i << "=" << val << ",";
        break;
      }
    }
    return o;
  }
  //! operator==
  bool operator==(BorderPLC const &plc) const
  {
    for (size_t i=0; i<6; ++i) {
      if (m_values[i]!=plc.m_values[i])
        return false;
    }
    return true;
  }
  //! operator!=
  bool operator!=(BorderPLC const &plc) const
  {
    return !(*this==plc);
  }
  //! the values
  std::vector<int> m_values;
};

//! a graphic style PLC
struct GraphicPLC {
  //! constructor
  GraphicPLC()
    : m_graphStyleId(0)
    , m_unknownId(0)
  {
  }
  //! constructor
  explicit GraphicPLC(std::vector<int> const &values)
    : m_graphStyleId(0)
    , m_unknownId(0)
  {
    if (values.size()!=2) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::GraphicPLC::GraphicPLC: bad value size\n"));
      return;
    }
    m_unknownId=values[0];
    m_graphStyleId=values[1];
  }
  //! returns the graphic id
  int getGraphicStyleId() const
  {
    return m_graphStyleId;
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, GraphicPLC const &plc)
  {
    if (plc.m_graphStyleId) o << "GS" << plc.m_graphStyleId << ",";
    if (plc.m_unknownId) o << "#unkn=" << plc.m_unknownId << ",";
    return o;
  }
  //! operator==
  bool operator==(GraphicPLC const &plc) const
  {
    return m_graphStyleId==plc.m_graphStyleId && m_unknownId==plc.m_unknownId;
  }
  //! operator!=
  bool operator!=(GraphicPLC const &plc) const
  {
    return !(*this==plc);
  }
  //! the graph style
  int m_graphStyleId;
  //! unknown id (always 0)
  int m_unknownId;
};

//! a text style PLC
struct TextPLC {
  //! constructor
  TextPLC()
    : m_textStyleId(0)
    , m_formatId(0)
    , m_unknownId(0)
    , m_flags(0)
  {
  }
  //! constructor
  explicit TextPLC(std::vector<int> const &values)
    : m_textStyleId(0)
    , m_formatId(0)
    , m_unknownId(0)
    , m_flags(0)
  {
    if (values.size()!=4) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::TextPLC::TextPLC: bad value size\n"));
      return;
    }
    m_textStyleId=values[0];
    m_formatId=values[1];
    m_unknownId=values[2];
    m_flags=values[3];
  }
  //! returns the alignement
  MWAWCell::HorizontalAlignment getHorizontalAlignment() const
  {
    switch (m_flags&3) {
    case 1:
      return MWAWCell::HALIGN_LEFT;
    case 2:
      return MWAWCell::HALIGN_CENTER;
    case 3:
      return MWAWCell::HALIGN_RIGHT;
    default:
    case 0: // none
      break;
    }
    return MWAWCell::HALIGN_DEFAULT;
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, TextPLC const &plc)
  {
    if (plc.m_textStyleId) o << "TS" << plc.m_textStyleId << ",";
    if (plc.m_formatId) o << "Fo" << plc.m_formatId << ",";
    if (plc.m_unknownId) o << "#unkn=" << plc.m_unknownId << ",";
    if (plc.m_flags) {
      int fl=plc.m_flags;
      switch (fl&3) {
      case 0: // none
        break;
      case 1:
        o << "align=left,";
        break;
      case 2:
        o << "align=center,";
        break;
      case 3:
        o << "align=right,";
        break;
      default:
        break;
      }
      fl &= 0xFFFC;
      if (fl) // find [04][03][0348][08]
        o << "fl=" << std::hex << fl << std::dec << ",";
    }
    return o;
  }
  //! operator==
  bool operator==(TextPLC const &plc) const
  {
    return m_textStyleId==plc.m_textStyleId && m_formatId==plc.m_formatId && m_unknownId==plc.m_unknownId &&
           m_flags==plc.m_flags;
  }
  //! operator!=
  bool operator!=(TextPLC const &plc) const
  {
    return !(*this==plc);
  }
  //! the text style
  int m_textStyleId;
  //! the format style
  int m_formatId;
  //! unknown id
  int m_unknownId;
  //! text flag
  int m_flags;
};

//! internal: a structure used to store a sheet in RagTime5SpreadsheetInternal
struct Sheet {
  /** a row: a list of cell map */
  struct Row {
    //! constructor
    Row(MWAWVec2i const &row, int plane)
      : m_rows(row)
      , m_columnsToDataMap()
    {
      // create the spreadsheet zone
      m_columnsToDataMap.insert(std::map<MWAWVec2i, CellContent>::value_type(MWAWVec2i(0,15999),CellContent(MWAWVec2i(0,m_rows[0]), plane)));
    }
    //! returns the rows
    MWAWVec2i const &getRows() const
    {
      return m_rows;
    }
    //! returns true if the row is empty
    bool isEmpty() const
    {
      for (auto it : m_columnsToDataMap) {
        if (it.second.hasContent())
          return false;
      }
      return true;
    }
    //! split columns if needed, so that we can insert cells correspond to the cols interval
    void splitColumns(MWAWVec2i const &cols)
    {
      // first find the first columns block
      auto it=m_columnsToDataMap.lower_bound(MWAWVec2i(-1,cols[0]));
      if (it==m_columnsToDataMap.end()) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Row::splitColumns: argh can not find any column for %d-%d\n", cols[0], cols[1]));
        return;
      }
      while (it!=m_columnsToDataMap.end()) {
        MWAWVec2i contentCols=it->first;
        if (cols[1]<contentCols[0]) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Row::splitColumns: argh somethings is bad when checking columns %d-%d\n", cols[0], cols[1]));
          return;
        }
        if (cols[0]<=contentCols[0] && contentCols[1]<=cols[1]) { // no need to split the cell
          ++it;
          continue;
        }
        CellContent content=it->second;
        int breakPos=(cols[0]>contentCols[0] && cols[0]<=contentCols[1]) ? 0 :
                     (cols[1]>=contentCols[0] && cols[1]<contentCols[1]) ? 1 : -1;
        if (breakPos==-1) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Row::splitColumns: argh can not find break pos %d-%d\n", cols[0], cols[1]));
          return;
        }
        m_columnsToDataMap.erase(contentCols);
        int newMinCol=cols[breakPos]+(breakPos==0?0:1);
        m_columnsToDataMap.insert
        (std::map<MWAWVec2i, CellContent>::value_type(MWAWVec2i(contentCols[0], newMinCol-1),content));
        // check if we need to update the cell position
        if (!content.m_isMerged && content.m_id[CellContent::Union]==-1)
          content.m_position[0]=newMinCol;
        else
          content.m_isMerged=true;
        it=m_columnsToDataMap.insert
           (std::map<MWAWVec2i, CellContent>::value_type(MWAWVec2i(newMinCol, contentCols[1]),content)).first;
      }
    }
    //! update the cells content type
    void update(MWAWVec2i const &cols, int id, int contentId, MWAWVec2i const &beginCellPos, std::set<MWAWVec2i> &unsetCell)
    {
      splitColumns(cols);
      auto it=m_columnsToDataMap.lower_bound(MWAWVec2i(-1,cols[0]));
      if (it==m_columnsToDataMap.end()) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Row::update: argh can not find any column for %d-%d\n", cols[0], cols[1]));
        return;
      }
      while (it!=m_columnsToDataMap.end()) {
        MWAWVec2i const cPos=it->first;
        if (cPos[0]>cols[1]) break;
        if (cPos[0]<cols[0]||cPos[1]>cols[1]) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Row::update: argh can insert some columns for %d-%d\n", cols[0], cols[1]));
          break;
        }
        CellContent &content=(it++)->second;
        if (content.m_isMerged) {
          unsetCell.insert(content.m_position);
          continue;
        }
        content.setContent(id, contentId);
        if (id==CellContent::Union && content.m_position!=beginCellPos) {
          content.m_position=beginCellPos;
          content.m_isMerged=true;
        }
      }
    }
    //! reset each row's cell position to new row position
    void resetMinRow(int row)
    {
      for (auto &it : m_columnsToDataMap) {
        if (it.second.m_id[CellContent::Union]==-1)
          it.second.m_position[1]=row;
        else
          it.second.m_isMerged=true;
      }
    }
    friend std::ostream &operator<<(std::ostream &o, Row const &row)
    {
      for (auto it : row.m_columnsToDataMap)
        o << it.first << ":" << it.second << ",";
      return o;
    }
    //! the rows (min-max)
    MWAWVec2i m_rows;
    //! the map columns to data
    std::map<MWAWVec2i, CellContent> m_columnsToDataMap;
  };

  /** a plane: a list of rows map */
  struct Plane {
    //! constructor
    explicit Plane(int plane)
      : m_plane(plane)
      , m_rowsToDataMap()
      , m_unitedCellMap()
    {
      // create the spreadsheet zone
      m_rowsToDataMap.insert(std::map<MWAWVec2i, Row>::value_type(MWAWVec2i(0,15999),Row(MWAWVec2i(0,15999), plane)));
    }
    //! returns the plane
    int getPlane() const
    {
      return m_plane;
    }
    //! returns true if the row is empty
    bool isEmpty() const
    {
      for (auto it : m_rowsToDataMap) {
        if (!it.second.isEmpty())
          return false;
      }
      return true;
    }

    //! returns the span value corresponding to an id
    MWAWVec2i getSpan(MWAWVec2i const &position) const
    {
      auto it=m_unitedCellMap.find(position);
      if (it!=m_unitedCellMap.end())
        return MWAWVec2i(it->second[0]-it->first[0]+1, it->second[1]-it->first[1]+1);
      return MWAWVec2i(1,1);
    }

    //! split rows if needed, so that we can insert cells correspond to the rows interval
    void splitRows(MWAWVec2i const &rows)
    {
      // first find the first rows block
      auto it=m_rowsToDataMap.lower_bound(MWAWVec2i(-1,rows[0]));
      if (it==m_rowsToDataMap.end()) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::splitRows: argh can not find any rowumn for %d-%d\n", rows[0], rows[1]));
        return;
      }
      while (it!=m_rowsToDataMap.end()) {
        MWAWVec2i contentRows=it->first;
        if (rows[1]<contentRows[0]) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::splitRows: argh somethings is bad when checking rows %d-%d\n", rows[0], rows[1]));
          return;
        }
        if (rows[0]<=contentRows[0] && contentRows[1]<=rows[1]) { // no need to split the cell
          ++it;
          continue;
        }
        Row rContent=it->second;
        int breakPos=(rows[0]>contentRows[0] && rows[0]<=contentRows[1]) ? 0 :
                     (rows[1]>=contentRows[0] && rows[1]<contentRows[1]) ? 1 : -1;
        if (breakPos==-1) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::splitRows: argh can not find break pos %d-%d\n", rows[0], rows[1]));
          return;
        }
        int newMinRow=rows[breakPos]+(breakPos==0?0:1);
        m_rowsToDataMap.erase(contentRows);
        rContent.m_rows=MWAWVec2i(contentRows[0], newMinRow-1);
        m_rowsToDataMap.insert(std::map<MWAWVec2i, Row>::value_type(rContent.m_rows,rContent));

        rContent.resetMinRow(newMinRow);
        rContent.m_rows=MWAWVec2i(newMinRow, contentRows[1]);
        it=m_rowsToDataMap.insert(std::map<MWAWVec2i, Row>::value_type(rContent.m_rows,rContent)).first;
      }
    }
    //! update the cells content type
    void update(Sheet const &sheet, MWAWBox2i const &box, int id, int contentId)
    {
      MWAWVec2i rows(box[0][1], box[1][1]), cols(box[0][0], box[1][0]);
      splitRows(rows);
      auto it=m_rowsToDataMap.lower_bound(MWAWVec2i(-1,rows[0]));
      if (it==m_rowsToDataMap.end()) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::update: argh can not find any rows for %d-%d\n", rows[0], rows[1]));
        return;
      }
      std::set<MWAWVec2i> unsetCell;
      while (it!=m_rowsToDataMap.end()) {
        MWAWVec2i const rPos=it->first;
        if (rPos[0]>rows[1]) break;
        if (rPos[0]<rows[0]||rPos[1]>rows[1]) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::update: argh can insert some rows for %d-%d\n", rows[0], rows[1]));
          break;
        }
        it->second.update(cols, id, contentId, box[0], unsetCell);
        ++it;
      }
      if (unsetCell.empty() || id==CellContent::GraphicStyle || id==CellContent::TextStyle ||
          id==CellContent::BorderPrevHStyle || id==CellContent::BorderPrevVStyle)
        return;
      // sometimes merged cell have value with no content
      if (id==CellContent::Value && contentId>0 && contentId<=static_cast<int>(sheet.m_valuesList.size()) &&
          sheet.m_valuesList[size_t(contentId-1)].m_type==0)
        return;
      // if the cells have been merged, we need to affect the next border to the original cell
      if (id==CellContent::BorderNextHStyle || id==CellContent::BorderNextVStyle) {
        for (auto cellPos : unsetCell) {
          it=m_rowsToDataMap.lower_bound(MWAWVec2i(-1,cellPos[1]));
          if (it==m_rowsToDataMap.end() || it->first[0]!=cellPos[1]) {
            MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::update: argh can not find a cell to set border: %dx%d\n", cellPos[0], cellPos[1]));
            continue;
          }
          Row &row=it->second;
          auto rIt=row.m_columnsToDataMap.lower_bound(MWAWVec2i(-1,cellPos[0]));
          if (rIt==row.m_columnsToDataMap.end() || rIt->first[0]!=cellPos[0] || rIt->second.m_id[CellContent::Union]<0) {
            MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::update: argh can not find a cell to set border: %dx%d(II)\n", cellPos[0], cellPos[1]));
            continue;
          }
          rIt->second.setContent(id,contentId);
        }
        return;
      }
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::Plane::update: argh can not set some cell for id=%d\n", id));
    }

    friend std::ostream &operator<<(std::ostream &o, Plane const &plane)
    {
      if (plane.m_rowsToDataMap.empty()) return o;
      for (auto it : plane.m_rowsToDataMap)
        o << "\t" << it.first << "[" << plane.m_plane << "]:" << it.second << "\n";
      return o;
    }

    //! the plane
    int m_plane;
    //! the map rows to data
    std::map<MWAWVec2i, Row> m_rowsToDataMap;
    //! the list of united cell: map from TL cell to RB cell
    std::map<MWAWVec2i, MWAWVec2i> m_unitedCellMap;
  };

  //! constructor
  Sheet()
    : m_name("")
    , m_textboxZoneId(0)
    , m_colWidthDef(56)
    , m_colWidthsMap()
    , m_rowHeightDef(13)
    , m_rowHeightsMap()
    , m_unitedCellMap()
    , m_valuesList()
    , m_planesList()
    , m_graphicPLCList()
    , m_defGraphicPLC()
    , m_textPLCList()
    , m_defTextPLC()
    , m_defaultFont(16, 12)
    , m_defaultParagraph()
    , m_pictureList()
    , m_isSent(false)
  {
  }
  //! returns a name corresponding to a plane
  std::string getName(int plane) const
  {
    std::stringstream s;
    s << m_name << "_" << plane;
    return s.str();
  }
  //! increase the number of planes if need
  void increasePlaneSizeIfNeeded(int newPlane)
  {
    if (newPlane<0) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::increasePlaneSizeIfNeeded: called with bad id=%d\n", newPlane));
      return;
    }
    for (auto plane=static_cast<int>(m_planesList.size()); plane<newPlane; ++plane) {
      if (plane>=100) {
        static bool first=true;
        if (first) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::increasePlaneSizeIfNeeded: sorry, 100 planes is the arbitrary maximum\n"));
          first=true;
        }
        return;
      }
      m_planesList.push_back(Plane(plane+1));
    }
  }
  //! returns the number of planes
  int getNumPlanes() const
  {
    int numPlanes=0;
    for (size_t plane=m_planesList.size(); plane>=1; --plane) {
      if (!m_planesList[plane-1].isEmpty())
        return static_cast<int>(plane);
    }
    return numPlanes;
  }

  //! stores a plc
  void setPLCValues(MWAWVec3i const &minPos, MWAWVec3i const &maxPos, int plcType, int plcId)
  {
    increasePlaneSizeIfNeeded(maxPos[2]);
    MWAWBox2i box(MWAWVec2i(minPos[0],minPos[1]),MWAWVec2i(maxPos[0],maxPos[1]));
    for (int plane=minPos[2]-1; plane<=maxPos[2]-1; ++plane) {
      if (plane<0 || plane>=static_cast<int>(m_planesList.size())) {
        static bool first=true;
        if (first) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::setPLCValues: plane %d seems bad\n", plane));
          first=false;
        }
        continue;
      }
      Plane &planeData=m_planesList[size_t(plane)];
      planeData.update(*this, box, plcType, plcId);
    }
  }

  //! stores an union of cells
  void setMergedCells(MWAWVec3i const &minPos, MWAWVec3i const &maxPos)
  {
    increasePlaneSizeIfNeeded(maxPos[2]);
    MWAWBox2i box(MWAWVec2i(minPos[0],minPos[1]),MWAWVec2i(maxPos[0],maxPos[1]));
    for (int plane=minPos[2]-1; plane<=maxPos[2]-1; ++plane) {
      if (plane<0 || plane>=static_cast<int>(m_planesList.size())) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::setMergedCells: plane %d seems bad\n", plane));
        continue;
      }
      Plane &planeData=m_planesList[size_t(plane)];
      planeData.m_unitedCellMap[box[0]]=box[1];
      planeData.update(*this, box, CellContent::Union, 1);
      if (minPos[1]!=maxPos[1]) // split the row to avoid sending merged cells
        planeData.splitRows(MWAWVec2i(minPos[1],minPos[1]));
    }
  }

  //! returns the row height in point
  float getRowHeight(int row) const
  {
    auto rIt=m_rowHeightsMap.lower_bound(MWAWVec2i(-1,row));
    if (rIt==m_rowHeightsMap.end() || rIt->first[0]>row || rIt->first[1]<row)
      return -m_rowHeightDef;
    return rIt->second;
  }
  //! sets the row heights
  void setRowsHeight(MWAWVec2i const &rows, float height)
  {
    if (rows[0]<0 || rows[1]<rows[0]) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::setRowsHeight: the rows %dx%d seems bad\n", rows[0], rows[1]));
      return;
    }
    m_rowHeightDef=m_rowHeightsMap[rows]=height;
  }
  //! returns the col width in point
  float getColWidth(int col) const
  {
    auto rIt=m_colWidthsMap.lower_bound(MWAWVec2i(-1,col));
    if (rIt==m_colWidthsMap.end() || rIt->first[0]>col || rIt->first[1]<col)
      return m_colWidthDef;
    return rIt->second;
  }
  //! returns the col width dimension in point
  std::vector<float> getColumnWidths(std::vector<int> &repeated) const
  {
    std::vector<float> widths;
    repeated.clear();
    int actPos=0;
    for (auto it : m_colWidthsMap) {
      int lastPos=it.first[1];
      if (lastPos<actPos) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::getColumnWidths: the position %d seems bad\n", lastPos));
        continue;
      }
      widths.push_back(it.second);
      repeated.push_back(lastPos+1-actPos);
      actPos=lastPos;
    }
    return widths;
  }
  //! sets the row widths
  void setColsWidth(MWAWVec2i const &cols, float width)
  {
    if (cols[0]<0 || cols[1]<cols[0]) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::setColsWidth: the cols %dx%d seems bad\n", cols[0], cols[1]));
      return;
    }
    m_colWidthDef=m_colWidthsMap[cols]=width;
  }
  //! returns the cell dimension
  MWAWVec2f getCellDimensions(MWAWVec2i const &position, int plane) const
  {
    MWAWVec2i maxPos=getBottomRightCell(position, plane);
    float width=0;
    for (int c=position[0]; c<maxPos[0]; ++c)
      width+=getColWidth(c);
    float height=0;
    for (int r=position[1]; r<maxPos[1]; ++r) {
      float h=getRowHeight(r);
      height+=h<0 ? -h : h;
    }
    return MWAWVec2f(width, height);
  }
  //! returns the bottom right cell
  MWAWVec2i getBottomRightCell(MWAWVec2i const &position, int plane) const
  {
    return position+getSpan(position, plane);
  }
  //! returns the span value corresponding to an id
  MWAWVec2i getSpan(MWAWVec2i const &position, int plane) const
  {
    if (plane<=0 || plane>static_cast<int>(m_planesList.size()))
      return MWAWVec2i(1,1);
    return m_planesList[size_t(plane-1)].getSpan(position);
  }

  //! returns the graphic id
  int getGraphicStyleId(int id) const
  {
    if (id<0 || id>=static_cast<int>(m_graphicPLCList.size())) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::getGraphicStyleId: unknown id=%d\n", id));
      return -1;
    }
    return m_graphicPLCList[size_t(id)].getGraphicStyleId();
  }
  //! retrieves the text plc
  bool getTextPLC(int id, TextPLC &plc) const
  {
    if (id<0 || id>=static_cast<int>(m_textPLCList.size())) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::Sheet::getTextPLC: unknown id=%d\n", id));
      return -1;
    }
    plc=m_textPLCList[size_t(id)];
    return true;
  }
  //! the sheet name
  std::string m_name;
  //! the text zone id
  int m_textboxZoneId;
  //! the default col width in point
  float m_colWidthDef;
  //! the col widths: map for cols positions to width in points
  std::map<MWAWVec2i, float> m_colWidthsMap;
  //! the default row height in point
  float m_rowHeightDef;
  //! the row heights: map from rows positions to height in points
  std::map<MWAWVec2i, float> m_rowHeightsMap;
  //! the list of united cell: map from TL cell to RB cell
  std::map<MWAWVec3i, MWAWVec3i> m_unitedCellMap;
  //! the list of values
  std::vector<CellValue> m_valuesList;
  //! the list of planes
  std::vector<Plane> m_planesList;
  //! the graph plc
  std::vector<GraphicPLC> m_graphicPLCList;
  //! the default graphic plc
  GraphicPLC m_defGraphicPLC;
  //! the text plc
  std::vector<TextPLC> m_textPLCList;
  //! the default text plc
  TextPLC m_defTextPLC;
  //! the default border plc (vertical and horizontal)
  BorderPLC m_defBordersPLC[2];
  //! the default font (Palatino, 12)
  MWAWFont m_defaultFont;
  //! the default paragraph
  MWAWParagraph m_defaultParagraph;
  //! the list of picture zone
  std::vector<RagTime5StructManager::ZoneLink> m_pictureList;
  //! a flag to know if the sheet has been sent
  bool m_isSent;
};

//
// parser
//

//! Internal: the helper to read a clustList
struct ClustListParser final : public RagTime5StructManager::DataParser {
  //! constructor
  ClustListParser(RagTime5ClusterManager &clusterManager, int fieldSize, std::string const &zoneName)
    : RagTime5StructManager::DataParser(zoneName)
    , m_clusterList()
    , m_fieldSize(fieldSize)
    , m_clusterManager(clusterManager)
  {
    if (fieldSize!=24 && fieldSize!=60) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ClustListParser::ClustListParser: bad data size\n"));
      m_fieldSize=0;
    }
  }
  //! destructor
  ~ClustListParser() final;
  //! return a name which can be used for debugging
  std::string getClusterName(int id) const
  {
    return m_clusterManager.getClusterName(id);
  }

  //! try to parse a data
  bool parseData(MWAWInputStreamPtr &input, long endPos, RagTime5Zone &/*zone*/, int /*n*/, libmwaw::DebugStream &f) final
  {
    long pos=input->tell();
    if (!m_fieldSize || endPos-pos!=m_fieldSize) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ClustListParser::parse: bad data size\n"));
      return false;
    }

    std::vector<int> listIds;
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ClustListParser::parse: can not read an cluster id\n"));
      f << "##clusterIds,";
      return false;
    }
    if (listIds[0]) {
      m_clusterList.push_back(listIds[0]);
      // a e,2003,200b, ... cluster
      f << getClusterName(listIds[0]) << ",";
    }
    unsigned long lVal=input->readULong(4); // c00..small number
    if ((lVal&0xc0000000)==0xc0000000)
      f << "f0=" << (lVal&0x3fffffff) << ",";
    else
      f << "f0*" << lVal << ",";
    if (m_fieldSize==24) {
      for (int i=0; i<8; ++i) { // f1=0|1, f2=f3=f4=0, f5=0|c, f6=0|d|e
        auto val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i << "=" << val << ",";
      }
      return true;
    }
    auto val=static_cast<int>(input->readLong(4)); // small int
    if (val) f << "f0=" << val << ",";
    for (int i=0; i<3; ++i) {
      float dim[4];
      for (auto &d : dim) d=float(input->readLong(4))/65536.f;
      MWAWBox2f box(MWAWVec2f(dim[0],dim[1]), MWAWVec2f(dim[2],dim[3]));
      if (box!=MWAWBox2f(MWAWVec2f(0,0),MWAWVec2f(0,0)))
        f << "dim" << i << "=" << box << ",";
    }
    return true;
  }

  //! the list of read cluster
  std::vector<int> m_clusterList;
private:
  //! the field size
  int m_fieldSize;
  //! the main zone manager
  RagTime5ClusterManager &m_clusterManager;
  //! copy constructor, not implemented
  ClustListParser(ClustListParser &orig) = delete;
  //! copy operator, not implemented
  ClustListParser &operator=(ClustListParser &orig) = delete;
};

ClustListParser::~ClustListParser()
{
}

//! Internal: the helper to read a cell values
struct ValuesParser final : public RagTime5StructManager::DataParser {
  //! constructor
  explicit ValuesParser(Sheet &sheet)
    : RagTime5StructManager::DataParser("SheetValue")
    , m_sheet(sheet)
  {
  }
  //! destructor
  ~ValuesParser() final;
  //! try to parse a data
  bool parseData(MWAWInputStreamPtr &input, long endPos, RagTime5Zone &/*zone*/, int n, libmwaw::DebugStream &debStream) final
  {
    long pos=input->tell();
    long fSz=endPos-pos;
    if (fSz<2) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: bad data size\n"));
      return false;
    }
    libmwaw::DebugStream f;
    RagTime5SpreadsheetInternal::CellValue cell;
    auto type=static_cast<int>(input->readULong(2));
    bool hasIndex[3]= {(type&0x40)!=0, (type&0x80)!=0, (type&0x2000)!=0};
    if (type&0x4E30)
      f << "fl" << std::hex << (type&0x4E30) << std::dec << ",";
    cell.m_type=(type&0x910F);
    bool ok=true;
    switch (cell.m_type) {
    case 0: // empty
      break;
    case 1:
    case 0xa: // componentId(pict)
      if (fSz<4) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: find bad size for long\n"));
        f << "###fSz[long],";
        ok=false;
        break;
      }
      cell.m_id=input->readULong(4);
      break;
    case 2: // find 800b88[47]8, so ?
      if (fSz<4) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: find bad size for long\n"));
        f << "###fSz[long],";
        ok=false;
        break;
      }
      cell.m_long=long(input->readULong(4));
      break;
    case 4: // number
    case 5: // date
    case 6: { // time
      if (fSz<10) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: find bad size for double\n"));
        f << cell << "###fSz[double],";
        ok=false;
        break;
      }
      bool isNan;
      if (!input->readDouble8(cell.m_double, isNan)) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: can not read a double\n"));
        f << "###double,";
        ok=false;
      }
      break;
    }
    case 7: {
      for (int i=0; i<3; ++i) {
        if (!hasIndex[i]) continue;
        if (input->tell()+4>endPos) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: can not read index\n"));
          f << "###index[string],";
          ok=false;
          break;
        }
        hasIndex[i]=false;
        auto val=static_cast<int>(input->readLong(4));
        if (!val) continue;
        if (i==0) // checkme
          cell.m_fieldId=val;
        else
          f << "f" << i << "=" << val << ",";
      }
      if (!ok)
        break;
      if (!RagTime5StructManager::readUnicodeString(input, endPos, cell.m_text)) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: can not read a string\n"));
        f << "###string,";
        ok=false;
      }
      break;
    }
    case 8:
    case 9: { // multiligne text zone
      if (fSz<4) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: find bad size for long2\n"));
        f << "###fSz[long],";
        ok=false;
        break;
      }
      cell.m_id=input->readULong(4);
      break;
    }
    default:
      break;
    }
    for (int i=0; ok && i<3; ++i) {
      if (!hasIndex[i]) continue;
      if (input->tell()+4>endPos) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: can not read an index\n"));
        f << "###index,";
        break;
      }
      auto val=static_cast<int>(input->readLong(4));
      if (!val) continue;
      if (i==0)
        cell.m_fieldId=val;
      else
        f << "f" << i << "=" << val << ",";
    }
    if (ok && input->tell()!=endPos) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: find extra data\n"));
      f << "###extra,";
    }
    cell.m_extra=f.str();
    if (n<=0) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::ValuesParser::parse: n value:%d seems bad\n", n));
    }
    else {
      if (n>static_cast<int>(m_sheet.m_valuesList.size()))
        m_sheet.m_valuesList.resize(size_t(n));
      m_sheet.m_valuesList[size_t(n-1)]=cell;
    }
    debStream << "V" << n << "," << cell;
    return true;
  }
  //! the actual sheet
  Sheet &m_sheet;
};

ValuesParser::~ValuesParser()
{
}

//! Internal: the helper to read a list of cell to paragraph/char/... data
struct CellPLCParser final : public RagTime5StructManager::DataParser {
  //! constructor
  CellPLCParser(Sheet &sheet, int which, int fieldSize, std::map<MWAWVec2i,int> const &numRowByPlanes)
    : RagTime5StructManager::DataParser(fieldSize==6 ? "SheetGrphPLC" : fieldSize==10 ? "SheetTxtPLC" : "SheetBordPLC")
    , m_which(which)
    , m_fieldSize(fieldSize)
    , m_row(0)
    , m_planes(1,1)
    , m_sheet(sheet)
    , m_numRowByPlanes(numRowByPlanes)
    , m_numRemainingRows(-1)
  {
    static int const expectedSize[]= {6,10,14,14};
    if (m_which<0 || m_which>=4 || m_fieldSize!=expectedSize[m_which]) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::CellPLCParser::CellPLCParser: bad field size\n"));
      m_which=-1;
    }
    if (!m_numRowByPlanes.empty()) {
      m_planes=m_numRowByPlanes.begin()->first;
      m_numRemainingRows=m_numRowByPlanes.begin()->second;
    }
  }
  //! destructor
  ~CellPLCParser() final;
  //! try to parse a data
  bool parseData(MWAWInputStreamPtr &input, long endPos, RagTime5Zone &zone, int n, libmwaw::DebugStream &f) final
  {
    long pos=input->tell();
    long fSz=endPos-pos;
    if (fSz<2 || (fSz%m_fieldSize)!=4) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::CellPLCParser::parse: bad data size\n"));
      return false;
    }
    int const maxRow = m_fieldSize==14 ? 16001 : 16000;
    auto numRow=static_cast<int>(input->readULong(2));
    MWAWVec2i planes=m_planes;
    MWAWVec2i rows(m_row, m_row+numRow-1);

    f << "R" << m_row+1;
    if (numRow!=1)
      f << "-" << m_row+numRow;
    f << ",";
    f << "planes=" << planes << ",";
    m_row += numRow;
    if (--m_numRemainingRows==0) {
      auto it=m_numRowByPlanes.find(m_planes);
      if (it!=m_numRowByPlanes.end() && (++it)!=m_numRowByPlanes.end()) {
        m_planes=it->first;
        m_numRemainingRows=it->second;
        m_row=0;
      }
    }
    if (m_numRemainingRows<0 && m_row>=maxRow) {
      // by default, suppose that we add one planes
      m_planes=MWAWVec2i(m_planes[1]+1,m_planes[1]+1);
      m_row=0;
    }
    auto N=static_cast<int>(input->readLong(2));
    f << "N=" << N << ",";
    if (fSz!=4+m_fieldSize*N) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::CellPLCParser::parse: N seems bad\n"));
      f << "###";
      return true;
    }
    libmwaw::DebugStream f1;
    libmwaw::DebugFile &ascii=zone.ascii();
    int col=0;
    std::vector<int> values;
    size_t const numValues=size_t(m_fieldSize-2)/2;
    values.resize(numValues);
    /* we limit the number of planes to the current number of planes
       or 20 to avoid creating 16000 sheets, just because the user has
       reset some style in all planes */
    int const maxPlanes=m_sheet.m_planesList.size()>20 ? int(m_sheet.m_planesList.size()+1) : 20;
    for (int i=0; i<N; ++i) {
      pos=input->tell();
      f1.str("");
      f1 << m_name << "-" << n << "-A" << i << ":";
      auto numCol=static_cast<int>(input->readLong(2));
      f1 << "C" << col+1;
      if (numCol!=1)
        f1 << "-"  << col+numCol;
      f1 << ",";
      MWAWVec2i cols(col, col+numCol-1);
      col+=numCol;
      for (auto &val : values) val=static_cast<int>(input->readLong(2));
      switch (m_which) {
      case 0: {
        GraphicPLC plc(values);
        if (plc!=m_sheet.m_defGraphicPLC) {
          f1 << plc;
          for (int plane=planes[0]; plane<=planes[1] && plane<=maxPlanes; ++plane)
            m_sheet.setPLCValues(MWAWVec3i(cols[0],rows[0],plane), MWAWVec3i(cols[1],rows[1],plane),
                                 RagTime5SpreadsheetInternal::CellContent::GraphicStyle, static_cast<int>(m_sheet.m_graphicPLCList.size()));
          m_sheet.m_graphicPLCList.push_back(plc);
        }
        else
          f1 << "def,";
        break;
      }
      case 1: {
        TextPLC plc(values);
        if (plc!=m_sheet.m_defTextPLC) {
          f1 << plc;
          for (int plane=planes[0]; plane<=planes[1] && plane<=maxPlanes; ++plane)
            m_sheet.setPLCValues(MWAWVec3i(cols[0],rows[0],plane), MWAWVec3i(cols[1],rows[1],plane),
                                 RagTime5SpreadsheetInternal::CellContent::TextStyle, static_cast<int>(m_sheet.m_textPLCList.size()));
          m_sheet.m_textPLCList.push_back(plc);
        }
        else
          f1 << "def,";
        break;
      }
      case 2: // vertical border
      case 3: { // horizontal border
        BorderPLC plc(values);
        if (plc!=m_sheet.m_defBordersPLC[m_which-2]) {
          f1 << plc;
          if (plc.isMergedBorder())
            break;
          int bordersId[2]= {plc.getBorderGraphicStyleId(true), plc.getBorderGraphicStyleId(false) };
          for (int wh=0; wh<2; ++wh) {
            if (bordersId[wh]<=0) continue;
            MWAWVec2i finalRows=rows;
            if (wh==0) {
              if (finalRows[0]>0)
                --finalRows[0];
              --finalRows[1];
            }
            else {
              if (finalRows[1]==160000)
                --finalRows[1];
            }
            if (finalRows[0]>finalRows[1])
              continue;
            for (int plane=planes[0]; plane<=planes[1] && plane<=maxPlanes; ++plane) {
              if (m_which==2) // time to invert rows and columns
                m_sheet.setPLCValues(MWAWVec3i(finalRows[0],cols[0],plane), MWAWVec3i(finalRows[1],cols[1],plane),
                                     RagTime5SpreadsheetInternal::CellContent::BorderPrevVStyle+(1-wh), bordersId[wh]);
              else
                m_sheet.setPLCValues(MWAWVec3i(cols[0],finalRows[0],plane), MWAWVec3i(cols[1],finalRows[1],plane),
                                     RagTime5SpreadsheetInternal::CellContent::BorderPrevHStyle+(1-wh), bordersId[wh]);
            }
          }
        }
        else
          f1 << "def,";
        break;
      }
      default:
        for (size_t j=0; j<values.size(); ++j) {
          if (values[j])
            f1 << "f" << j << "=" << values[j] << ",";
        }
        break;
      }
      input->seek(pos+m_fieldSize, librevenge::RVNG_SEEK_SET);
      ascii.addPos(pos);
      ascii.addNote(f1.str().c_str());
    }
    return true;
  }
  //! the type: 0=graphicStyles, 1=textStyles, 2=vertical border, 3=horizontal border
  int m_which;
  //! the field size
  int m_fieldSize;
  //! the actual row
  int m_row;
  //! the actual plane set
  MWAWVec2i m_planes;
  //! the actual sheet
  Sheet &m_sheet;
  //! the number of row by planes
  std::map<MWAWVec2i,int> const m_numRowByPlanes;
  //! the remaining row in the planes
  int m_numRemainingRows;
private:
  //! copy constructor, not implemented
  CellPLCParser(CellPLCParser &orig) = delete;
  //! copy operator, not implemented
  CellPLCParser &operator=(CellPLCParser &orig) = delete;
};

CellPLCParser::~CellPLCParser()
{
}

////////////////////////////////////////
//! Internal: the state of a RagTime5Spreadsheet
struct State {
  //! constructor
  State()
    : m_idSheetMap()
    , m_newSheetId(0)
  {
  }
  //! returns a new sheet name
  std::string getNewSheetName() const
  {
    std::stringstream s;
    s << "Sheet" << ++m_newSheetId;
    return s.str();
  }
  //! map data id to sheet zone
  std::map<int, std::shared_ptr<Sheet> > m_idSheetMap;
protected:
  //! a int uses to generate unique sheet id
  mutable int m_newSheetId;
};

}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
RagTime5Spreadsheet::RagTime5Spreadsheet(RagTime5Parser &parser)
  : m_mainParser(parser)
  , m_structManager(m_mainParser.getStructManager())
  , m_styleManager(m_mainParser.getStyleManager())
  , m_parserState(parser.getParserState())
  , m_state(new RagTime5SpreadsheetInternal::State)
{
}

RagTime5Spreadsheet::~RagTime5Spreadsheet()
{
}

int RagTime5Spreadsheet::version() const
{
  return m_parserState->m_version;
}

int RagTime5Spreadsheet::numPages() const
{
  return m_state->m_idSheetMap.empty() ? 0 : 1;
}

////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool RagTime5Spreadsheet::readSheetDimensions(RagTime5SpreadsheetInternal::Sheet &sheet, RagTime5Zone &zone, RagTime5ClusterManager::Link const &link)
{
  MWAWEntry const &entry=zone.m_entry;
  if (!entry.valid() || link.m_fieldSize!=24 || link.m_fieldSize*link.m_N>entry.length()) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSheetDimensions: the zone seems bad\n"));
    if (entry.valid()) {
      libmwaw::DebugFile &ascFile=zone.ascii();
      libmwaw::DebugStream f;
      f << "Entries(SheetDims)[" << zone << "]:###";
      zone.m_isParsed=true;
      ascFile.addPos(entry.begin());
      ascFile.addNote(f.str().c_str());
    }
    return false;
  }
  libmwaw::DebugFile &ascFile=zone.ascii();
  libmwaw::DebugStream f;
  f << "Entries(SheetDims)[" << zone << "]:";
  zone.m_isParsed=true;
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());
  MWAWInputStreamPtr input=zone.getInput();
  input->setReadInverted(!zone.m_hiLoEndian);
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  long endPos=entry.end();

  int actCPos=0;
  for (int i=0; i<link.m_N; ++i) {
    long pos=input->tell();
    f.str("");
    f << "SheetDims-" << i+1 << ":";
    long newCPos=input->readLong(4);
    float value=float(input->readLong(4))/65536.f;
    if (newCPos<0 || newCPos>32000) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSheetDimensions: find unexpected newCPos\n"));
      f << "###newCPos=" << newCPos << ",";
    }
    else if (newCPos==0)
      f << "empty,";
    else if (newCPos<=16000) { // between 1 and 16000
      MWAWVec2i cols(actCPos, int(newCPos-1));
      sheet.setColsWidth(cols, value);
      f << "C" << cols << ",";
    }
    else { // between 16001 and 32000
      MWAWVec2i rows(actCPos-16000, int(newCPos-16000-1));
      sheet.setRowsHeight(rows, value);
      f << "R" << rows << ",";
    }
    f << "dim=" << value << ",";
    float dim[2]; // often 0x0 for row and 2x2 for col
    for (auto &d : dim) d=float(input->readLong(4))/65536.f;
    if (dim[0]>0||dim[1]>0) f << "sz?=" << dim[0] << "x" << dim[1] << ",";
    auto val=long(input->readULong(4));
    if (val) {
      f << "f0=" << std::hex << (val&0xFFFFFF) << std::dec;
      if (static_cast<unsigned long>(val)&0xFF000000)
        f << ":" << (val>>24); // 0|2
      f << ",";
    }
    for (int j=0; j<2; ++j) { // f1=0|1
      val=input->readLong(2);
      if (val)
        f << "f" << i+1 << "=" << val << ",";
    }
    if (newCPos>0 && newCPos<=32000)
      actCPos=int(newCPos);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  long pos=input->tell();
  if (pos!=endPos) {
    // no rare
    ascFile.addPos(pos);
    ascFile.addNote("SheetDims:end");
  }
  input->setReadInverted(false);
  return true;
}

bool RagTime5Spreadsheet::readCellBlocks(RagTime5SpreadsheetInternal::Sheet &sheet, RagTime5Zone &zone, RagTime5ClusterManager::Link const &link, bool isUnion)
{
  MWAWEntry const &entry=zone.m_entry;
  std::string const wh(isUnion ? "SheetUnion" : "SheetRefBlock");
  if (!entry.valid() || (link.m_fieldSize!=22&&link.m_fieldSize!=24) || link.m_fieldSize*link.m_N>entry.length()) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readCellBlocks: the zone seems bad\n"));
    if (entry.valid()) {
      libmwaw::DebugFile &ascFile=zone.ascii();
      libmwaw::DebugStream f;
      f << "Entries(" << wh << ")[" << zone << "]:###";
      zone.m_isParsed=true;
      ascFile.addPos(entry.begin());
      ascFile.addNote(f.str().c_str());
    }
    return false;
  }
  libmwaw::DebugFile &ascFile=zone.ascii();
  libmwaw::DebugStream f;
  f << "Entries(" << wh << ")[" << zone << "]:";
  zone.m_isParsed=true;
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());
  MWAWInputStreamPtr input=zone.getInput();
  input->setReadInverted(!zone.m_hiLoEndian);
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  long endPos=entry.end();
  int const numEnd=link.m_fieldSize==22 ? 1 : 2;

  for (int i=0; i<link.m_N; ++i) {
    // checkme: it is possible that the list of valid block is maintained in the SheetRefPos
    long pos=input->tell();
    f.str("");
    f << wh << "-" << i+1 << ":";
    auto val=static_cast<int>(input->readLong(2)); // always 0?
    auto type=static_cast<int>(input->readLong(2));
    if (val==0 && type==0) { // no data,
      f << "_";
      input->seek(pos+link.m_fieldSize, librevenge::RVNG_SEEK_SET);
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      continue;
    }
    if (val) f << "f0=" << val << ",";
    if (type!=1) f << "type=" << type << ",";
    int dim[4], plane[2];
    for (auto &d : dim) d=int(input->readULong(2));
    for (auto &p : plane) p=int(input->readULong(2));
    if (dim[0]==0 && dim[1]==0 && dim[2]==0 && dim[3]==0) {
      f << "_";
      input->seek(pos+link.m_fieldSize, librevenge::RVNG_SEEK_SET);
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      continue;
    }
    if (plane[1]<=plane[0]) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readCellBlocks: the block planes seem bad\n"));
      f << "###plane,";
    }
    else if (dim[2]>=dim[0] || dim[3]>=dim[1]) {
      if (isUnion) {
        MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readCellBlocks: the block seems bad\n"));
        f << "###";
      }
      else if (dim[2]==dim[0] || dim[3]==dim[1]) //
        f << "#";
    }
    else if (isUnion && (dim[0]!=dim[2]+1 || dim[1]!=dim[3]+1))
      sheet.setMergedCells(MWAWVec3i(dim[2]-1,dim[3]-1,plane[0]),MWAWVec3i(dim[0]-2,dim[1]-2,plane[1]-1));

    // 1 means first line, 16001 means last line
    f << "cells?=" << MWAWBox2i(MWAWVec2i(dim[2],dim[3]),MWAWVec2i(dim[0],dim[1])) << ",";
    if (plane[1]!=plane[0]+1)
      f << "planes=" << plane[0] << "<->" << plane[1]-1 << ",";
    else if (plane[0]!=1)
      f << "plane=" << plane[0] << ",";
    unsigned long zoneId=input->readULong(4);
    if (zoneId==0x2000000) // normal no zone
      ;
    else if ((zoneId>>24)==2)
      f << "zone[id]=" << (zoneId&0xFFFFFF) << ",";
    else {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readCellBlocks: the zone id seems bad\n"));
      f << "###zoneId=" << std::hex << input->readULong(4) << std::dec << ",";
    }
    for (int j=0; j<numEnd; ++j) { // always 0
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "f" << j+3 << "=" << val << ",";
    }
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  long pos=input->tell();
  if (pos!=endPos) {
    // extra data seems rare, but possible...
    f.str("");
    f << wh << ":end";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  input->setReadInverted(false);
  return true;
}

bool RagTime5Spreadsheet::readValuesTree(RagTime5SpreadsheetInternal::Sheet &sheet, RagTime5Zone &zone,
    RagTime5ClusterManager::Link const &link, int rootId, MWAWVec3i const &maxPos)
{
  MWAWEntry const &entry=zone.m_entry;
  if (!entry.valid() || link.m_fieldSize!=8 || link.m_fieldSize*link.m_N>entry.length()) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readValuesTree: the zone seems bad\n"));
    if (entry.valid()) {
      libmwaw::DebugFile &ascFile=zone.ascii();
      libmwaw::DebugStream f;
      f << "Entries(SheetVTree)[" << zone << "]:###";
      zone.m_isParsed=true;
      ascFile.addPos(entry.begin());
      ascFile.addNote(f.str().c_str());
    }
    return false;
  }
  libmwaw::DebugFile &ascFile=zone.ascii();
  libmwaw::DebugStream f;
  f << "Entries(SheetVTree)[" << zone << "]:";
  zone.m_isParsed=true;
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());
  MWAWInputStreamPtr input=zone.getInput();
  input->setReadInverted(!zone.m_hiLoEndian);
  /* a binary tree, all leaf have the same level. Each node shares
     format coord-leftChild-type-rightChild where:
     - coord=0..2,
     - type=0(internal node) or type=11(leaf),
     - left(resp. right) child corresponds to 0(resp. 1) value,
     - bits defining each coord are stored in reversed order(to make
       the tree grows easily if needed)
   */
  std::stack<int> idStack;
  std::stack<MWAWVec3i> coordStack;
  std::set<int> idSeenSet;
  idStack.push(rootId);
  coordStack.push(MWAWVec3i(0,0,0));
  while (!idStack.empty()) {
    int id=idStack.top();
    MWAWVec3i coord=coordStack.top();
    idStack.pop();
    coordStack.pop();
    if (idSeenSet.find(id)!=idSeenSet.end() || id<=0 || id>link.m_N) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readValuesTree: id %d is bad\n", id));
      continue;
    }
    idSeenSet.insert(id);
    long pos=entry.begin()+8*(id-1);
    input->seek(pos, librevenge::RVNG_SEEK_SET);

    f.str("");
    f << "SheetVTree-VT" << id << ":";
    if (id==rootId) f << "root,";

    int child[2];
    unsigned long value=input->readULong(4);
    auto actCoord=int(value>>24);
    child[0]=int(value&0xFFFFFF);
    value=input->readULong(4);
    auto type=int(value>>24);
    child[1]=int(value&0xFFFFFF);

    if (actCoord<0 || actCoord>2) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readValuesTree: coord %d seems bad\n", actCoord));
      f << "###coord=" << actCoord << ",";
    }
    else if (type!=0 && type!=0x11) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readValuesTree: unknown type %d\n", type));
      f << "###type=" << type << ",";
    }
    else {
      coord[actCoord]*=2;
      for (int i=0; i<2; ++i) {
        int cId=child[i];
        if (!cId) {
          f << "_,";
          continue;
        }
        if (i==1) ++coord[actCoord];
        if (type==0x11) {
          f << "V" << cId << "[C" << coord << "],";
          if (coord[0]<=0 || coord[0]>maxPos[0] || coord[1]<=0 || coord[1]>maxPos[1] || coord[2]<=0 || coord[2]>maxPos[2]) {
            MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readValuesTree: the final pos seems bad\n"));
            f << "###pos,";
          }
          else
            sheet.setPLCValues(coord-MWAWVec3i(1,1,0),coord-MWAWVec3i(1,1,0), RagTime5SpreadsheetInternal::CellContent::Value, cId);
          continue;
        }
        if (cId>=1 && cId<=link.m_N && idSeenSet.find(cId)==idSeenSet.end()) {
          idStack.push(cId);
          coordStack.push(coord);
        }
        else {
          MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readValuesTree: find bad child\n"));
          f << "###";
        }
        f << "VT" << cId << ",";
      }
    }
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }

#ifdef DEBUG_WITH_FILES
  auto it=idSeenSet.begin();
  for (int i=1; i<=link.m_N; ++i) {
    if (it!=idSeenSet.end() && *it==i) {
      ++it;
      continue;
    }
    ascFile.addPos(entry.begin()+(i-1)*8);
    ascFile.addNote("SheetVTree:_");
  }
#endif

  if (link.m_N*8!=entry.length()) { // no frequent, but can happens
    ascFile.addPos(entry.begin()+link.m_N*8);
    ascFile.addNote("SheetVTree:end");
  }
  input->setReadInverted(false);
  return true;
}

////////////////////////////////////////////////////////////
//
// Low level
//
////////////////////////////////////////////////////////////
bool RagTime5Spreadsheet::readCellRefPos(RagTime5Zone &zone, RagTime5ClusterManager::Link const &link)
{
  MWAWEntry const &entry=zone.m_entry;
  if (!entry.valid() || link.m_fieldSize!=10 || link.m_fieldSize*link.m_N>entry.length()) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readCellRefPos: the zone seems bad\n"));
    if (entry.valid()) {
      libmwaw::DebugFile &ascFile=zone.ascii();
      libmwaw::DebugStream f;
      f << "Entries(SheetRefPos)[" << zone << "]:###";
      zone.m_isParsed=true;
      ascFile.addPos(entry.begin());
      ascFile.addNote(f.str().c_str());
    }
    return false;
  }
  libmwaw::DebugFile &ascFile=zone.ascii();
  libmwaw::DebugStream f;
  f << "Entries(SheetRefPos)[" << zone << "]:";
  zone.m_isParsed=true;
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());
  MWAWInputStreamPtr input=zone.getInput();
  input->setReadInverted(!zone.m_hiLoEndian);
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  long endPos=entry.end();

  for (int i=0; i<link.m_N; ++i) {
    long pos=input->tell();
    f.str("");
    f << "SheetRefPos-" << i+1 << ":";
    f << "id1=" << input->readLong(4) << ","; // 1-40 maybe col ?
    auto fl=static_cast<int>(input->readULong(2)); // [0145][08][0-f][0-f]
    if (fl) f << "fl=" << std::hex << fl << std::dec << ",";
    f << "id2=" << input->readLong(4) << ","; // maybe row ?
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  long pos=input->tell();
  if (pos!=endPos) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readCellRefPos: find extra data\n"));
    ascFile.addPos(pos);
    ascFile.addNote("SheetRefPos:end###");
  }
  input->setReadInverted(false);
  return true;
}

////////////////////////////////////////////////////////////
// interface send function
////////////////////////////////////////////////////////////
bool RagTime5Spreadsheet::send(int zoneId, MWAWListenerPtr listener, MWAWPosition const &pos, int partId)
{
  auto it=m_state->m_idSheetMap.find(zoneId);
  if (it==m_state->m_idSheetMap.end() || !it->second) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::send: can not find sheet for zone %d\n", zoneId));
    return false;
  }
  return send(*it->second, listener, pos, partId);
}

bool RagTime5Spreadsheet::send(RagTime5SpreadsheetInternal::Sheet &sheet, MWAWListenerPtr listener, MWAWPosition const &position, int partId)
{
  sheet.m_isSent=true;
  if (!listener)
    listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::send: can not find the listener\n"));
    return false;
  }

  if (partId>1) {
    static bool first=true;
    if (first) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::send: send partial sheet is not implemented, send all sheets\n"));
      first=false;
    }
  }
  // time to update default value
  if (sheet.m_defTextPLC.m_textStyleId)
    m_styleManager->updateTextStyles(sheet.m_defTextPLC.m_textStyleId,sheet.m_defaultFont,sheet.m_defaultParagraph);
  MWAWBox2f box=MWAWBox2f(MWAWVec2f(0,0), position.size());
  MWAWSpreadsheetEncoder spreadsheetEncoder;
  auto *spreadsheetListener=new MWAWSpreadsheetListener(*m_parserState, box, &spreadsheetEncoder);
  MWAWSpreadsheetListenerPtr sheetListener(spreadsheetListener);
  spreadsheetListener->startDocument();
  if (sheet.m_name.empty()) // fixme
    sheet.m_name=m_state->getNewSheetName();
  for (int plane=1; plane<=sheet.getNumPlanes(); ++plane) {
    if (plane>static_cast<int>(sheet.m_planesList.size()))
      break;
    auto const &data=sheet.m_planesList[size_t(plane-1)];
    if (data.isEmpty()) continue;
    std::vector<int> repeatedWidths;
    std::vector<float> colWidths=sheet.getColumnWidths(repeatedWidths);
    spreadsheetListener->openSheet(colWidths, librevenge::RVNG_POINT, repeatedWidths, sheet.getName(plane));
    int actRow=-1;
    for (auto rIt : data.m_rowsToDataMap) {
      if (rIt.second.isEmpty()) continue;

      MWAWVec2i rowPos=rIt.first;
      auto const row=rIt.second;
      if (rowPos[0]>actRow+1) {
        // must not happen, so suppose all row have same size
        spreadsheetListener->openSheetRow(sheet.getRowHeight(actRow+1), librevenge::RVNG_POINT, rowPos[0]-actRow-1);
        spreadsheetListener->closeSheetRow();
      }

      while (rowPos[0]<=rowPos[1]) {
        // we must check that all row have the same height, if not, we must send them separatly
        MWAWVec2i blockRow=rowPos;
        auto hIt=sheet.m_rowHeightsMap.lower_bound(MWAWVec2i(-1,blockRow[0]));
        if (hIt!=sheet.m_rowHeightsMap.end() && hIt->first[0]>=blockRow[0] && hIt->first[1]<blockRow[1])
          blockRow[1]=hIt->first[1];
        // ok let send the current block
        spreadsheetListener->openSheetRow(sheet.getRowHeight(blockRow[0]), librevenge::RVNG_POINT, blockRow[1]-blockRow[0]+1);
        actRow=blockRow[1];
        for (auto cIt : row.m_columnsToDataMap) {
          RagTime5SpreadsheetInternal::CellContent const &cContent=cIt.second;
          if (cContent.isMergedCell()) continue;

          bool isUnion=cContent.m_id[RagTime5SpreadsheetInternal::CellContent::Union]>=0;
          send(sheet, cContent, isUnion ? 1 : cIt.first[1]-cIt.first[0]+1, sheetListener);
        }
        spreadsheetListener->closeSheetRow();
        rowPos[0]=blockRow[1]+1;
      }
    }
    spreadsheetListener->closeSheet();
  }
  spreadsheetListener->endDocument();

  MWAWEmbeddedObject object;
  if (spreadsheetEncoder.getBinaryResult(object))
    listener->insertPicture(position, object);
  return true;
}

bool RagTime5Spreadsheet::send
(RagTime5SpreadsheetInternal::Sheet &sheet, RagTime5SpreadsheetInternal::CellContent const &cContent, int numRepeated,
 MWAWSpreadsheetListenerPtr &listener)
{
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::send: can not find the listener\n"));
    return false;
  }

  MWAWCell cell;
  MWAWCellContent content;
  cell.setPosition(cContent.m_position);
  cell.setVAlignment(MWAWCell::VALIGN_CENTER);
  // the span
  if (cContent.m_id[RagTime5SpreadsheetInternal::CellContent::Union]>=0)
    cell.setNumSpannedCells(sheet.getSpan(cContent.m_position, cContent.m_plane));
  // graphic style: background color
  int graphicId=cContent.m_id[RagTime5SpreadsheetInternal::CellContent::GraphicStyle];
  if (graphicId>=0)
    graphicId=sheet.getGraphicStyleId(graphicId);
  else
    graphicId=sheet.m_defGraphicPLC.getGraphicStyleId();
  MWAWColor color;
  if (graphicId>0 && m_styleManager->getCellBackgroundColor(graphicId, color))
    cell.setBackgroundColor(color);
  // the border
  for (int i=0; i<4; ++i) {
    int const static wh[]= {RagTime5SpreadsheetInternal::CellContent::BorderPrevVStyle,
                            RagTime5SpreadsheetInternal::CellContent::BorderNextVStyle,
                            RagTime5SpreadsheetInternal::CellContent::BorderPrevHStyle,
                            RagTime5SpreadsheetInternal::CellContent::BorderNextHStyle
                           };
    int bId=cContent.m_id[wh[i]];
    if (bId<0) bId=sheet.m_defBordersPLC[i<2 ? 0 : 1].getBorderGraphicStyleId((i%2)==1);
    if (bId<=0) continue;
    MWAWBorder border;
    if (!m_styleManager->getCellBorder(bId, border))
      continue;
    int const final[]= {libmwaw::LeftBit, libmwaw::RightBit, libmwaw::TopBit, libmwaw::BottomBit};
    cell.setBorders(final[i], border);
  }
  // the value
  RagTime5SpreadsheetInternal::CellValue value;
  if (cContent.m_id[RagTime5SpreadsheetInternal::CellContent::Value]>0 &&
      cContent.m_id[RagTime5SpreadsheetInternal::CellContent::Value]<=static_cast<int>(sheet.m_valuesList.size()))
    value=sheet.m_valuesList[size_t(cContent.m_id[RagTime5SpreadsheetInternal::CellContent::Value]-1)];
  value.update(cell, content);
  // the number/text/format plc
  RagTime5SpreadsheetInternal::TextPLC plc;
  MWAWFont font(sheet.m_defaultFont);
  MWAWParagraph para(sheet.m_defaultParagraph);

  if (cContent.m_id[RagTime5SpreadsheetInternal::CellContent::TextStyle]>=0)
    sheet.getTextPLC(cContent.m_id[RagTime5SpreadsheetInternal::CellContent::TextStyle], plc);
  if (plc.m_formatId)
    m_styleManager->updateCellFormat(plc.m_formatId, cell);
  if (plc.m_textStyleId)
    m_styleManager->updateTextStyles(plc.m_textStyleId,font,para);
  auto align=plc.getHorizontalAlignment();
  if (align!=MWAWCell::HALIGN_DEFAULT)
    cell.setHAlignment(align);
  cell.setFont(font);
  listener->openSheetCell(cell, content, numRepeated);
  if (value.m_type==7) { // small text zone
    listener->setFont(font);
    listener->setParagraph(para);
    listener->insertUnicodeString(value.m_text);
  }
  else if (value.m_type==9) // big text zone
    m_mainParser.send(sheet.m_textboxZoneId, listener, MWAWPosition(), 0, static_cast<int>(value.m_id&0xFFFFFF));
  else if (value.m_type==0xa) { // a zone
    if (value.m_id>=static_cast<unsigned long>(sheet.m_pictureList.size()) ||
        sheet.m_pictureList[size_t(value.m_id)].m_dataId==0) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::send: the picture list seems bad\n"));
    }
    else {
      std::string cellName=sheet.getName(cContent.m_plane)+"."+MWAWCell::getBasicCellName(sheet.getBottomRightCell(cContent.m_position,cContent.m_plane));
      MWAWPosition position(MWAWVec2f(0,0), sheet.getCellDimensions(cContent.m_position, cContent.m_plane), librevenge::RVNG_POINT);
      position.setAnchorToCell(librevenge::RVNGString(cellName.c_str()));
      auto const &link=sheet.m_pictureList[size_t(value.m_id)];

      auto type=m_mainParser.getClusterType(link.m_dataId);
      if (type==RagTime5ClusterManager::Cluster::C_PictureZone)
        m_mainParser.send(link.m_dataId, listener, position, int(link.getSubZoneId(0)&0xFFFFFF));
      else if (type==RagTime5ClusterManager::Cluster::C_Script) {
        static bool first=true;
        if (first) {
          first=false;
          MWAW_DEBUG_MSG(("RagTime5Spreadsheet::send: arrgh sending script child is not implemented\n"));
        }
      }
      else {
        // let try to create a graphic object to represent the content
        MWAWBox2f box(MWAWVec2f(0,0), position.size());
        MWAWGraphicEncoder graphicEncoder;
        MWAWGraphicListenerPtr graphicListener(new MWAWGraphicListener(*m_parserState, box, &graphicEncoder));
        graphicListener->startDocument();
        MWAWPosition graphicPos;
        graphicPos.m_anchorTo = MWAWPosition::Page;
        m_mainParser.send(link.m_dataId, graphicListener, graphicPos, int(link.getSubZoneId(0)&0xFFFFFF));
        graphicListener->endDocument();

        MWAWEmbeddedObject picture;
        if (graphicEncoder.getBinaryResult(picture))
          listener->insertPicture(position, picture);
      }
    }
  }
  listener->closeSheetCell();
  return true;
}

void RagTime5Spreadsheet::flushExtra()
{
#ifdef DEBUG
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::flushExtra: can not find a listener\n"));
    return;
  }
  MWAWPosition position(MWAWVec2f(0,0), MWAWVec2f(100,100), librevenge::RVNG_POINT);
  position.m_anchorTo=MWAWPosition::Char;
  for (auto it : m_state->m_idSheetMap) {
    if (!it.second || it.second->m_isSent) continue;
    static bool first=true;
    if (first) {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::flushExtra: find some unsent spreadsheets %d, ...\n", it.first));
      first=false;
    }
    send(*it.second, listener, position);
  }
#endif
}

////////////////////////////////////////////////////////////
// cluster parser
////////////////////////////////////////////////////////////

namespace RagTime5SpreadsheetInternal
{

//! low level: the spreadsheet cluster data
struct ClusterSpreadsheet final : public RagTime5ClusterManager::Cluster {
  //! constructor
  ClusterSpreadsheet()
    : RagTime5ClusterManager::Cluster(C_SpreadsheetZone)
    , m_dimensionLink()
    , m_valuesLink()
    , m_valuesTreeLink()
    , m_valuesTreeRoot(0)
    , m_valuesMaxPos(0,0,0)
    , m_graphPLCLink()
    , m_graphPLCNumRowByPlanesMap()
    , m_textPLCLink()
    , m_textPLCNumRowByPlanesMap()
  {
  }
  //! destructor
  ~ClusterSpreadsheet() final;
  //! the dimension link
  RagTime5ClusterManager::Link m_dimensionLink;
  //! the list of values link
  RagTime5ClusterManager::Link m_valuesLink;
  //! the value tree link
  RagTime5ClusterManager::Link m_valuesTreeLink;
  //! the value tree root
  int m_valuesTreeRoot;
  //! the maximum values position
  MWAWVec3i m_valuesMaxPos;
  //! the graph PLC link
  RagTime5ClusterManager::Link m_graphPLCLink;
  //! the list of row graph plc by planes
  std::map<MWAWVec2i,int> m_graphPLCNumRowByPlanesMap;
  //! the text PLC link
  RagTime5ClusterManager::Link m_textPLCLink;
  //! the list of row text plc by planes
  std::map<MWAWVec2i,int> m_textPLCNumRowByPlanesMap;
  //! the border PLC link(vertical then horizontal)
  RagTime5ClusterManager::Link m_borderPLCLink[2];
  //! the list of row border plc by planes
  std::map<MWAWVec2i,int> m_borderPLCNumRowByPlanesMap[2];
  //! the reference block/cell union/reference pos link
  RagTime5ClusterManager::Link m_blockLinks[3];
  //! cluster links list: 0 picture link(size 10), 1 parent link (size 24)
  RagTime5ClusterManager::Link m_clusterLink[2];
};

ClusterSpreadsheet::~ClusterSpreadsheet()
{
}

//
//! low level: parser of main spreadsheet cluster
//
struct SpreadsheetCParser final : public RagTime5ClusterManager::ClusterParser {
  //! constructor
  SpreadsheetCParser(RagTime5ClusterManager &parser, int type)
    : ClusterParser(parser, type, "ClustSheet")
    , m_cluster(new ClusterSpreadsheet)
    , m_sheet(new Sheet)
    , m_what(-1)
    , m_linkId(-1)
    , m_blockType(0)
    , m_borderType(0)
    , m_fieldName("")
    , m_defaultPLCValues()
    , m_PLCNumRowByPlanesMap()
  {
  }
  //! destructor
  ~SpreadsheetCParser() final;
  //! return the spreadsheet cluster
  std::shared_ptr<ClusterSpreadsheet> getSpreadsheetCluster()
  {
    return m_cluster;
  }
  //! return the current cluster
  std::shared_ptr<RagTime5ClusterManager::Cluster> getCluster() final
  {
    return m_cluster;
  }
  //! return the spreadsheet
  std::shared_ptr<Sheet> getSpreadsheet()
  {
    return m_sheet;
  }
  //! end of a start zone call
  void endZone() final
  {
    if (m_link.empty())
      return;
    switch (m_linkId) {
    case 0:
      if (m_cluster->m_nameLink.empty())
        m_cluster->m_nameLink=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops the name link is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 1: // picture link
    case 2: // parent link (graphic or pipeline)
      if (m_cluster->m_clusterLink[m_linkId-1].empty())
        m_cluster->m_clusterLink[m_linkId-1]=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops the cluster link %d is already set\n", m_linkId));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 3:
      if (m_cluster->m_dimensionLink.empty())
        m_cluster->m_dimensionLink=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops the dimension link is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 4:
      if (m_blockType==1 && m_cluster->m_blockLinks[0].empty())
        m_cluster->m_blockLinks[0]=m_link;
      else if (m_blockType==2 && m_cluster->m_blockLinks[1].empty())
        m_cluster->m_blockLinks[1]=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops the two block links are already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 5:
      if (m_cluster->m_blockLinks[2].empty())
        m_cluster->m_blockLinks[2]=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops the last block links is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 6:
      if (m_cluster->m_valuesTreeLink.empty())
        m_cluster->m_valuesTreeLink=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops the values tree link is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 8:
      if (m_link.m_fieldSize==6 && m_cluster->m_graphPLCLink.empty()) {
        m_cluster->m_graphPLCLink=m_link;
        m_cluster->m_graphPLCNumRowByPlanesMap=m_PLCNumRowByPlanesMap;
        m_sheet->m_defGraphicPLC=GraphicPLC(m_defaultPLCValues);
      }
      else if (m_link.m_fieldSize==10 && m_cluster->m_textPLCLink.empty()) {
        m_cluster->m_textPLCLink=m_link;
        m_cluster->m_textPLCNumRowByPlanesMap=m_PLCNumRowByPlanesMap;
        m_sheet->m_defTextPLC=TextPLC(m_defaultPLCValues);
      }
      else if (m_link.m_fieldSize==14 && m_borderType==1 && m_cluster->m_borderPLCLink[0].empty()) { // first vertical
        m_cluster->m_borderPLCLink[0]=m_link;
        m_cluster->m_borderPLCNumRowByPlanesMap[0]=m_PLCNumRowByPlanesMap;
        m_sheet->m_defBordersPLC[0]=BorderPLC(m_defaultPLCValues);
      }
      else if (m_link.m_fieldSize==14 && m_borderType==2 && m_cluster->m_borderPLCLink[1].empty()) { // second horizontal
        m_cluster->m_borderPLCLink[1]=m_link;
        m_cluster->m_borderPLCNumRowByPlanesMap[1]=m_PLCNumRowByPlanesMap;
        m_sheet->m_defBordersPLC[1]=BorderPLC(m_defaultPLCValues);
      }
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops find unexpected PLC link with size %d\n",
                        m_link.m_fieldSize));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 9:
      if (m_cluster->m_valuesLink.empty())
        m_cluster->m_valuesLink=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::endZone: oops the values link is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    default:
      m_cluster->m_linksList.push_back(m_link);
      break;
    }
  }
  //! parse a zone
  bool parseZone(MWAWInputStreamPtr &input, long fSz, int N, int flag, libmwaw::DebugStream &f) final
  {
    m_what=m_linkId=-1;
    m_fieldName="";
    if (N==-5)
      return parseHeaderZone(input,fSz,N,flag,f);
    if (N<0) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseZone: expected N value\n"));
      f << "###N=" << N << ",";
      return true;
    }
    return parseDataZone(input, fSz, N, flag, f);
  }
  //! parse a field
  bool parseField(RagTime5StructManager::Field const &field, int /*m*/, libmwaw::DebugStream &f) final
  {
    if (!m_fieldName.empty())
      f << m_fieldName << ",";
    switch (m_what) {
    case 1: // list link
      if (field.m_type==RagTime5StructManager::Field::T_LongList && field.m_fileType==0xce842) {
        f << "pos=[";
        for (auto val : field.m_longList)
          f << val << ",";
        f << "],";
        m_link.m_longList=field.m_longList;
        break;
      }
      if (field.m_type==RagTime5StructManager::Field::T_Unstructured && field.m_fileType==0xce017) {
        // a small value 2|4|a|1c|40
        f << "unkn="<<field.m_extra << ",";
        break;
      }
      // only with long2 list and with unk=[10-15]
      if (field.m_type==RagTime5StructManager::Field::T_LongList && field.m_fileType==0xcf042) {
        f << "unkn=[";
        for (auto val : field.m_longList) {
          if (val==0)
            f << "_,";
          else
            f << val << ",";
        }
        break;
      }

      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseField: find unexpected list link field\n"));
      f << "###" << field;
      break;
    case 2: // plc link
      if (field.m_type==RagTime5StructManager::Field::T_LongList && field.m_fileType==0xce842) {
        f << "pos=[";
        for (auto val : field.m_longList)
          f << val << ",";
        f << "],";
        m_link.m_longList=field.m_longList;
        break;
      }
      if (field.m_type==RagTime5StructManager::Field::T_FieldList && field.m_fileType==0x1671845) {
        f << "nData[byPlane]=["; // find only 0|1000X,[3e7f|3e80]0001: probably a selection
        for (auto const &child : field.m_fieldList) {
          if (child.m_type==RagTime5StructManager::Field::T_LongList && child.m_fileType==0xce842) {
            int actPlanes=1;
            for (auto val : child.m_longList) {
              auto nPlanes=static_cast<int>(val>>16);
              int nData=(val&0xFFFF);
              m_PLCNumRowByPlanesMap[MWAWVec2i(actPlanes,actPlanes+nPlanes-1)]=nData;
              actPlanes+=nPlanes;
              f << nData;
              if (nPlanes!=1)
                f << "[" << nPlanes << "]";
              f << ",";
            }
            if (actPlanes!=16001) {
              MWAW_DEBUG_MSG(("RagTime5GraphInternal::SpreadsheetCParser::parseField: the number of planes seems bad\n"));
              f << "###";
              m_PLCNumRowByPlanesMap.clear();
            }
            continue;
          }
          MWAW_DEBUG_MSG(("RagTime5GraphInternal::SpreadsheetCParser::parseField: find unexpected child[fSz=91]\n"));
          f << "##[" << child << "],";
        }
        f << "],";
        break;
      }
      if (field.m_type==RagTime5StructManager::Field::T_LongList && field.m_fileType==0x1671817) {
        f << "default=[";
        for (auto val : field.m_longList) {
          m_defaultPLCValues.push_back(int(val));
          f << val << ",";
        }
        f << "],";
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseField: find unexpected fSz69.. field\n"));
      f << "###" << field;
      break;
    default:
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseField: find unexpected field\n"));
      f << "###" << field;
      break;
    }
    return true;
  }
protected:
  //! parse a data block
  bool parseDataZone(MWAWInputStreamPtr &input, long fSz, int N, int flag, libmwaw::DebugStream &f)
  {
    // seems to begin by 34,[58]^{0|1},69,71,16,
    f << "fl=" << std::hex << flag << std::dec << ",";
    long pos=input->tell();
    //long endPos=pos+fSz-6;
    int val;
    long linkValues[4];
    std::string mess;
    m_link.m_N=N;
    switch (fSz) {
    case 16:
      for (int i=0; i<2; ++i) { // either 0,0 or g1=g0+1
        val=static_cast<int>(input->readLong(4));
        if (val)
          f << "g" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readLong(2)); // 4 or 8
      if (val!=4) f << "g2=" << val << ",";
      break;
    case 28:
    case 29:
    case 30:
    case 32:
    case 34:
    case 36: {
      if (!readLinkHeader(input, fSz, m_link, linkValues, mess)) {
        if (fSz==36) {
          input->seek(pos, librevenge::RVNG_SEEK_SET);
          val=static_cast<int>(input->readLong(4));
          long type=static_cast<int>(input->readULong(4));
          if (val==0x35800 && type==0x1454857) {
            f << "type=" << std::hex << type << std::dec << ",";
            for (int i=0; i<2; ++i) { // g1=1669817
              val=static_cast<int>(input->readLong(4));
              if (val) f << "g" << i << "=" << std::hex << val << std::dec << ",";
            }
            val=static_cast<int>(input->readULong(2));
            if (val)
              f << "fileType1=" << std::hex << val << std::dec << ",";
            // increasing sequence
            for (int i=0; i<3; ++i) { // g0=d, g1=g0+1, g2=g1+1
              val=static_cast<int>(input->readLong(4));
              if (val) f << "g" << i+2 << "=" << val << ",";
            }
            break;
          }
        }
        f << "###fType=" << std::hex << m_link.m_fileType[0] << std::dec << ",";
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: the field fSz28... type seems bad\n"));
        return true;
      }
      long expectedFileType1=0;
      m_what=1;
      if (m_link.m_fileType[0]==0x35800)
        m_fieldName="zone:longs";
      else if (m_link.m_fileType[0]==0x3e800)
        m_fieldName="list:longs0";
      else if (m_link.m_fileType[0]==0x3c052) {
        if (linkValues[0]!=0x1454877) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: find unexpected linkValue[0]\n"));
          f << "#lValues0,";
        }
        // linkValues[2]=5|8|9
        expectedFileType1=0x50;
        m_fieldName="zone:longs2";
      }
      else if (fSz==30 && m_link.m_fieldSize==12) { // find link to checkbox, ...
        expectedFileType1=0xd0;
        m_fieldName="pictureList";
        m_link.m_name="SheetPictureLst";
        m_link.m_type=RagTime5ClusterManager::Link::L_ClusterLink;
        m_linkId=1;
      }
      else if (fSz==32 && m_link.m_fileType[0]==0) {
        expectedFileType1=0x200;
        m_fieldName="unicode";
        m_linkId=0;
        m_link.m_type=RagTime5ClusterManager::Link::L_UnicodeList;
      }
      else if (fSz==34 && m_link.m_fileType[0]==0) {
        expectedFileType1=0x10;
        m_fieldName=m_link.m_name="values";
        m_linkId=9;
      }
      else if (fSz==34 && m_link.m_fieldSize==0xa) {
        expectedFileType1=-1;
        m_linkId=5;
        m_fieldName=m_link.m_name="sheetRefPos";
      }
      else if (fSz==34 && (m_link.m_fileType[1]&0xFFF7)==0x50 &&
               (m_link.m_fieldSize==0x16 || m_link.m_fieldSize==0x18)) {
        expectedFileType1=-1;
        m_linkId=4;
        ++m_blockType;
        m_fieldName=m_link.m_name=(m_blockType==1 ? "sheetRefBlock" : m_blockType==2 ? "sheetUnion" : "###unknownBlock");
        val=static_cast<int>(input->readULong(4)); // always 1
        if (val!=1)
          f << "g0=" << val << ",";
      }
      else if (fSz==34 && m_link.m_fieldSize==0x18 && (m_link.m_fileType[1]&0xFFF7)==0x40) {
        m_what=-1;
        expectedFileType1=-1;
        m_linkId=3;
        m_fieldName=m_link.m_name="sheetDims";
        val=static_cast<int>(input->readULong(4));
        if (val==32000) // always 0|32000
          f << "num[data32000],";
        else if (val)
          f << "num[data]=" << val << ",";
      }
      else if (fSz==36 && m_link.m_fileType[0]==0) { // parent link: graphic or pipeline link
        expectedFileType1=0x10;
        m_linkId=2;
        m_link.m_name=m_fieldName="parentList";
      }
      else {
        f << "###fType=" << m_link << ",";
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: the field fSz28 type seems bad\n"));
        return true;
      }
      if (expectedFileType1>=0 && (m_link.m_fileType[1]&0xFFD7)!=expectedFileType1) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: fileType1 seems odd[fSz=28...]\n"));
        f << "###fileType1=" << std::hex << m_link.m_fileType[1] << std::dec << ",";
      }
      m_link.m_fileType[0]=0;
      f << m_link << "," << mess;
      if (fSz==29) {
        val=static_cast<int>(input->readLong(1));
        if (val!=1) // always 1
          f << "g0=" << val << ",";
        break;
      }
      break;
    }
    case 58: {
      f << "root=VT" << N << ",";
      if (m_cluster->m_valuesTreeRoot) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: the value tree root is already set\n"));
        f << "###";
      }
      else
        m_cluster->m_valuesTreeRoot=N;
      val=static_cast<int>(input->readLong(2)); // always 1
      if (val!=1) f << "g0=" << val << ",";
      m_link.m_N=static_cast<int>(input->readLong(4));
      long actPos=input->tell();
      m_linkId=6;
      m_fieldName=m_link.m_name="VTree";
      if (!readLinkHeader(input, fSz, m_link, linkValues, mess)) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: can not read link for fSz58\n"));
        input->seek(actPos+30, librevenge::RVNG_SEEK_SET);
        f << "###link,";
      }
      else {
        f << m_link << "," << mess;
        m_link.m_fileType[0]=0;
        if ((m_link.m_fileType[1]&0xFFD7)!=0x40) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: fileType1 seems odd[fSz58]\n"));
          f << "###fileType1=" << std::hex << m_link.m_fileType[1] << std::dec << ",";
        }
      }
      val=static_cast<int>(input->readLong(4)); // 9-c
      if (val) f << "N2=" << val << ",";
      f << "num=[";
      for (int i=0; i<4; ++i) {
        val=static_cast<int>(input->readULong(2));
        if (!val)
          f << "_,";
        else
          f << val << ",";
      }
      f << "],";

      int dim[3];
      for (auto &d : dim) d=static_cast<int>(input->readULong(2));
      MWAWVec3i maxCell(dim[0],dim[1],dim[2]);
      if (m_cluster->m_valuesTreeLink.empty())
        m_cluster->m_valuesMaxPos=MWAWVec3i(dim[0],dim[1],dim[2]);
      f << "cell[max]=" << maxCell << ",";
      val=static_cast<int>(input->readLong(4)); // always 1?
      if (val!=1)
        f << "g2=" << val << ",";
      break;
    }
    case 68:
      for (int i=0; i<2; ++i) { // always 0
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << "f" << i << "=" << val << ",";
      }
      for (int i=0; i<2; ++i) { // f2=1, f3=0|a big number
        val=static_cast<int>(input->readLong(4));
        if (val)
          f << "f" << i+2 << "=" << val << ",";
      }
      val=static_cast<int>(input->readULong(4));
      if (val!=0x1646042) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: fileType0 seems odd[fSz68]\n"));
        f << "###fileType0=" << std::hex << val << std::dec << ",";
      }
      for (int i=0; i<4; ++i) { // always 0
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << "f" << i+4 << "=" << val << ",";
      }
      f << "num0=[";
      for (int i=0; i<3; ++i) { // small number
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << val << ",";
        else
          f << "_,";
      }
      f << "],";
      val=static_cast<int>(input->readULong(4)); // always 1
      if (val!=1) f << "f8=" << val << ",";
      for (int i=0; i<2; ++i) { // always 0
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << "f" << i+9 << "=" << val << ",";
      }
      f << "num1=[";
      for (int i=0; i<10; ++i) { // find X,_,_,X,X,_,_,X,X,_ where X are some small numbers
        val=static_cast<int>(input->readLong(1));
        if (val)
          f << val << ",";
        else
          f << "_,";
      }
      f << "],";
      f << "num2=[";
      for (int i=0; i<7; ++i) { // first always 0, other some ints
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << val << ",";
        else
          f << "_,";
      }
      f << "],";
      break;
    case 69:
    case 71: {
      m_what=2;
      if (!readLinkHeader(input, fSz, m_link, linkValues, mess)) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: can not read link for fSz69...\n"));
        input->seek(pos+26, librevenge::RVNG_SEEK_SET);
        f << "###link,";
      }
      else  {
        if ((m_link.m_fileType[1]&0xFFD7)!=0x8000) {
          MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: fileType1 seems odd[fSz69...]\n"));
          f << "###fileType1=" << std::hex << m_link.m_fileType[1] << std::dec << ",";
        }
        f << m_link << ",";
      }
      val=static_cast<int>(input->readLong(4)); // always 1
      if (val!=1)
        f << "g0=" << val << ",";
      m_link.m_fieldSize=static_cast<int>(input->readLong(2));
      val=static_cast<int>(input->readULong(2));
      if ((val==0x3e80 && (m_link.m_fieldSize==6 || m_link.m_fieldSize==10)) ||
          (val==0x3e81 && m_link.m_fieldSize==14)) {
        m_defaultPLCValues.clear();
        m_PLCNumRowByPlanesMap.clear();
        m_linkId=8;
        m_fieldName=(m_link.m_fieldSize==6 ? "sheetGrphPLC" : m_link.m_fieldSize==10 ? "sheetTxtPLC" : "sheetBorderPLC");
        if (m_link.m_fieldSize==14) {
          ++m_borderType;
          m_fieldName+=m_borderType==1 ? "[vert]" : "[hori]";
        }
        m_link.m_name=m_fieldName;
      }
      else {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: fileType2 seems odd[fSz69...]\n"));
        f << "###fileType2=" << std::hex << val << std::dec << ",";
      }
      val=static_cast<int>(input->readLong(2)); // always 1
      if (val!=1)
        f << "g2=" << val << ",";
      val=static_cast<int>(input->readLong(4)); // 1-2
      if (val!=2)
        f << "g3=" << val << ",";
      val=static_cast<int>(input->readULong(4));
      if (val!=0x34800) {
        MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: fileType3 seems odd[fSz69...]\n"));
        f << "###fileType3=" << std::hex << val << std::dec << ",";
      }
      for (int i=0; i<9; ++i) { // h6=32
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << "h" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readLong(1)); // always 1
      if (val!=1)
        f << "h9=" << val << ",";
      if (fSz==69)
        break;
      val=static_cast<int>(input->readLong(2)); // always 0
      if (val)
        f << "h10=" << val << ",";
      break;
    }
    default:
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseDataZone: find unexpected file size\n"));
      f << "###fSz=" << fSz << ",";
      break;
    }
    if (!m_fieldName.empty())
      f << m_fieldName << ",";
    return true;
  }
  //! parse the header zone
  bool parseHeaderZone(MWAWInputStreamPtr &input, long fSz, int N, int flag, libmwaw::DebugStream &f)
  {
    f << "header, fl=" << std::hex << flag << std::dec << ",";
    m_fieldName="header";
    m_what=0;
    if (N!=-5 || m_dataId!=0 || fSz != 134) {
      f << "###N=" << N << ",fSz=" << fSz << ",";
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseHeaderZone: find unexpected main field\n"));
      return true;
    }
    int val;
    for (int i=0; i<2; ++i) { // always 0?
      val=static_cast<int>(input->readLong(2));
      if (val) f << "f" << i+1 << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(2));
    f << "id=" << val << ",";
    val=static_cast<int>(input->readULong(2));
    if (m_type>0 && val!=m_type) {
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseHeaderZone: unexpected zone type\n"));
      f << "##zoneType=" << std::hex << val << std::dec << ",";
    }
    for (int i=0; i<9; ++i) { // f3=0|7-b, f4=0|8-b
      val=static_cast<int>(input->readULong(2));
      if (val)
        f << "f" << i+2 << "=" << val << ",";
    }
    for (int i=0; i<2; ++i) { // fl0=0|1, fl1=0|1
      val=static_cast<int>(input->readULong(1));
      if (val)
        f << "fl" << i << "=" << val << ",";
    }
    val=static_cast<int>(input->readULong(2)); // [02][08]0[12c]
    if (val) f << "fl2=" << std::hex << val << std::dec << ",";
    for (int i=0; i<12; ++i) { // g1=2, g3=0|3, g5=3|4, g7=g5+1, g9=g7+1, g11=h9+1
      val=static_cast<int>(input->readULong(2));
      if (val)
        f << "g" << i << "=" << val << ",";
    }
    std::vector<int> listIds;
    if (!RagTime5StructManager::readDataIdList(input, 2, listIds)) {
      f << "##field,";
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseHeaderZone: can not read the field definitions\n"));
      return true;
    }
    else if (listIds[0] || listIds[1]) { // fielddef and fieldpos
      RagTime5ClusterManager::Link fieldLink;
      fieldLink.m_type=RagTime5ClusterManager::Link::L_ClusterLink;
      fieldLink.m_ids=listIds;
      m_cluster->m_fieldClusterLink=fieldLink;
      f << "formula[" << fieldLink << "],";
    }
    for (int i=0; i<2; ++i) { // h1=0|8
      val=static_cast<int>(input->readULong(2));
      if (val)
        f << "h" << i << "=" << val << ",";
    }
    val=static_cast<int>(input->readULong(2)); // 10|110
    if (val) f << "fl3=" << std::hex << val << std::dec << ",";
    for (int i=0; i<2; ++i) { // 0
      val=static_cast<int>(input->readULong(2)); // 0
      if (val) f << "h" << i+2 << "=" << std::hex << val << std::dec << ",";
    }
    val=static_cast<int>(input->readULong(4));
    if (val!=0x34800)
      f << "#type1=" << std::hex << val << std::dec << ",";
    for (int i=0; i<9; ++i) { // h10=32
      val=static_cast<int>(input->readLong(2));
      if (val) f << "h" << i+4 << "=" << val << ",";
    }
    val=static_cast<int>(input->readULong(2)); // always 1
    if (val!=1)
      f << "num[planes]=" << val << ",";
    val=static_cast<int>(input->readULong(4)); // always 1
    if (val!=1)
      f << "l1=" << val << ",";
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      f << "##text,";
      MWAW_DEBUG_MSG(("RagTime5SpreadsheetInternal::SpreadsheetCParser::parseHeaderZone: can not read the text zone\n"));
      return true;
    }
    else if (listIds[0]) { // text zone
      m_sheet->m_textboxZoneId=listIds[0];
      m_cluster->m_clusterIdsList.push_back(listIds[0]);
      f << "clusterId[text]=" << getClusterName(listIds[0]) << ",";
    }
    for (int i=0; i<3; ++i) { // l1=0|7-9, l3=0-8-9
      val=static_cast<int>(input->readLong(4));
      if (val) f << "l" << i+2 << "=" << val << ",";
    }
    for (int i=0; i<6; ++i) { // l4=3|5, l5=0|1
      val=static_cast<int>(input->readLong(2));
      if (val) f << "l" << i+5 << "=" << val << ",";
    }
    return true;
  }

  //! the current cluster
  std::shared_ptr<ClusterSpreadsheet> m_cluster;
  //! the sheet
  std::shared_ptr<Sheet> m_sheet;
  //! a index to know which field is parsed :  0: main, 1: list, 2: fSz69...
  int m_what;
  /** the link id:
      0: unicode,
      1: picture link,
      2: cluster link unknown,
      3: the dimensions link,
      4: the cell block links
      5: the cell reference position link
      6: the value tree link
      7: not used
      8: the plc link
      9: the list of values */
  int m_linkId;
  /** the actual block id: 1: referenced block, 2: union cell */
  int m_blockType;
  /** the actual border type id: 1: vertical border, 2: horizontal border */
  int m_borderType;
  //! the actual field name
  std::string m_fieldName;
  //! the default plc values
  std::vector<int> m_defaultPLCValues;
  //! the list of row plc by planes
  std::map<MWAWVec2i,int> m_PLCNumRowByPlanesMap;
private:
  //! copy constructor (not implemented)
  SpreadsheetCParser(SpreadsheetCParser const &orig) = delete;
  //! copy operator (not implemented)
  SpreadsheetCParser &operator=(SpreadsheetCParser const &orig) = delete;
};

SpreadsheetCParser::~SpreadsheetCParser()
{
}

}

std::shared_ptr<RagTime5ClusterManager::Cluster> RagTime5Spreadsheet::readSpreadsheetCluster(RagTime5Zone &zone, int zoneType)
{
  auto clusterManager=m_mainParser.getClusterManager();
  if (!clusterManager) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSpreadsheetCluster: oops can not find the cluster manager\n"));
    return std::shared_ptr<RagTime5ClusterManager::Cluster>();
  }
  RagTime5SpreadsheetInternal::SpreadsheetCParser parser(*clusterManager, zoneType);
  if (!clusterManager->readCluster(zone, parser) || !parser.getSpreadsheetCluster() || !parser.getSpreadsheet()) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSpreadsheetCluster: oops can not find the cluster\n"));
    return std::shared_ptr<RagTime5ClusterManager::Cluster>();
  }
  auto cluster=parser.getSpreadsheetCluster();
  m_mainParser.checkClusterList(cluster->m_clusterIdsList);

  if (!cluster->m_dataLink.empty()) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSpreadsheetCluster: oops do not how to parse the main data\n"));
  }
  auto sheet=parser.getSpreadsheet();
  // values
  if (!cluster->m_valuesLink.empty()) {
    RagTime5SpreadsheetInternal::ValuesParser valuesParser(*sheet);
    m_mainParser.readListZone(cluster->m_valuesLink, valuesParser);
  }
  /* dimensions, cell blocks and finally the value tree

     note: it is important to read the merged cell before the value list and other PLC*/
  for (int w=0; w<5; ++w) {
    RagTime5ClusterManager::Link link=w==0 ? cluster->m_dimensionLink :
                                      w==4 ? cluster->m_valuesTreeLink : cluster->m_blockLinks[w-1];
    if (link.m_ids.empty())
      continue;
    int cId=link.m_ids[0];
    std::shared_ptr<RagTime5Zone> dataZone=m_mainParser.getDataZone(cId);
    if (!dataZone || !dataZone->m_entry.valid() ||
        dataZone->getKindLastPart(dataZone->m_kinds[1].empty())!="ItemData") {
      if (dataZone && dataZone->getKindLastPart()=="ItemData" && link.m_N==0)
        continue;
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSpreadsheetCluster: the %dth data zone %d seems bad\n", w, cId));
      continue;
    }
    if (w==0)
      readSheetDimensions(*sheet, *dataZone, link);
    else if (w==4)
      readValuesTree(*sheet, *dataZone, link, cluster->m_valuesTreeRoot, cluster->m_valuesMaxPos);
    else if (w==1 || w==2) // 1: formula block, 2: union block
      readCellBlocks(*sheet, *dataZone, link, w==2);
    else // 3: ref positions
      readCellRefPos(*dataZone, link);
  }

  // PLC
  for (int i=0; i<4; ++i) {
    auto link = i==0 ? cluster->m_graphPLCLink : i==1 ? cluster->m_textPLCLink : cluster->m_borderPLCLink[i-2];
    if (link.empty())
      continue;
    auto const &numRowByPlanes=i==0 ? cluster->m_graphPLCNumRowByPlanesMap : i==1 ? cluster->m_textPLCNumRowByPlanesMap :
                               cluster->m_borderPLCNumRowByPlanesMap[i-2];
    RagTime5SpreadsheetInternal::CellPLCParser plcParser(*sheet, i, link.m_fieldSize, numRowByPlanes);
    m_mainParser.readListZone(link, plcParser);
  }
  // formula + other ?
  if (!cluster->m_fieldClusterLink.empty())
    m_mainParser.getClusterManager()->readFieldClusters(cluster->m_fieldClusterLink);
  // pictures list
  if (!cluster->m_clusterLink[0].m_ids.empty()) {
    auto dataZone=m_mainParser.getDataZone(cluster->m_clusterLink[0].m_ids[0]);
    if (!dataZone || !dataZone->m_entry.valid() ||
        dataZone->getKindLastPart(dataZone->m_kinds[1].empty())!="ItemData") {
      MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSpreadsheetCluster: the picture zone %d seems bad\n",
                      cluster->m_clusterLink[0].m_ids[0]));
    }
    else {

      m_mainParser.readClusterLinkList(*dataZone, cluster->m_clusterLink[0], sheet->m_pictureList);
    }
  }
  // parent zones:  graphic or pipeline
  if (!cluster->m_clusterLink[1].empty()) {
    RagTime5SpreadsheetInternal::ClustListParser linkParser(*clusterManager, 24, "SheetParentLst");
    m_mainParser.readListZone(cluster->m_clusterLink[1], linkParser);
    m_mainParser.checkClusterList(linkParser.m_clusterList);
  }

  if (!cluster->m_nameLink.empty()) {
    std::map<int, librevenge::RVNGString> idToStringMap;
    m_mainParser.readUnicodeStringList(cluster->m_nameLink, idToStringMap);
  }

  for (auto const &lnk : cluster->m_linksList) {
    if (lnk.m_type==RagTime5ClusterManager::Link::L_List) {
      m_mainParser.readListZone(lnk);
      continue;
    }
    std::stringstream s;
    s << "Sheet_data" << lnk.m_fieldSize;
    RagTime5StructManager::DataParser defaultParser(lnk.m_name.empty() ? s.str() : lnk.m_name);
    m_mainParser.readFixedSizeZone(lnk, defaultParser);
  }

  if (m_state->m_idSheetMap.find(zone.m_ids[0])!=m_state->m_idSheetMap.end()) {
    MWAW_DEBUG_MSG(("RagTime5Spreadsheet::readSpreadsheetCluster: the sheet %d already exists\n",
                    zone.m_ids[0]));
  }
  else
    m_state->m_idSheetMap[zone.m_ids[0]]=sheet;
  return cluster;
}

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