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

#include <librevenge/librevenge.h>

#include "MWAWCell.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWHeader.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSpreadsheetListener.hxx"
#include "MWAWSubDocument.hxx"

#include "MultiplanParser.hxx"

/** Internal: the structures of a MultiplanParser */
namespace MultiplanParserInternal
{
////////////////////////////////////////
//! Internal: the state of a MultiplanParser
struct State {
  //! constructor
  State()
    : m_font()
    , m_maximumCell()
    , m_columnPositions()
    , m_cellPositions()
    , m_cellPositionsSet()
    , m_posToLinkMap()
    , m_posToNameMap()
    , m_posToSharedDataSeen()
  {
  }
  //! returns the column width in point
  std::vector<float> getColumnsWidth() const;
  //! the default font
  MWAWFont m_font;
  //! the maximumCell
  MWAWVec2i m_maximumCell;
  //! the columns begin position in point
  std::vector<int> m_columnPositions;
  //! the header/footer/printer message entries
  MWAWEntry m_hfpEntries[3];
  //! the positions of each cell: a vector for each row
  std::vector<std::vector<int> > m_cellPositions;
  //! the list of all position (use for checking)
  std::set<int> m_cellPositionsSet;
  //! the different main spreadsheet zones
  MWAWEntry m_entries[9];
  //! the list of link instruction
  std::map<int, MWAWCellContent::FormulaInstruction> m_posToLinkMap;
  //! the map name's pos to name's cell instruction
  std::map<int, MWAWCellContent::FormulaInstruction> m_posToNameMap;
  //! a set a shared data already seen
  std::set<int> m_posToSharedDataSeen;
};

std::vector<float> State::getColumnsWidth() const
{
  std::vector<float> res;
  bool first=true;
  int lastPos=0;
  float const defWidth=64.f;
  for (auto p : m_columnPositions) {
    if (first) {
      first=false;
      continue;
    }
    if (p<lastPos)
      res.push_back(defWidth);
    else
      res.push_back(float(p-lastPos));
    lastPos=p;
  }
  if (res.size()<64) res.resize(64, defWidth);
  return res;
}

////////////////////////////////////////
//! Internal: the subdocument of a MultiplanParserInternal
class SubDocument final : public MWAWSubDocument
{
public:
  SubDocument(MultiplanParser &parser, MWAWInputStreamPtr const &input, MWAWEntry const &entry)
    : MWAWSubDocument(&parser, input, entry)
    , m_parser(parser)
  {
  }

  //! destructor
  ~SubDocument() final {}

  //! operator!=
  bool operator!=(MWAWSubDocument const &doc) const final;

  //! the parser function
  void parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType type) final;

protected:
  /** the main parser */
  MultiplanParser &m_parser;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType /*type*/)
{
  if (!listener.get()) {
    MWAW_DEBUG_MSG(("MultiplanParser::SubDocument::parse: no listener\n"));
    return;
  }

  long pos = m_input->tell();
  m_parser.sendText(m_zone);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}

bool SubDocument::operator!=(MWAWSubDocument const &doc) const
{
  if (MWAWSubDocument::operator!=(doc)) return true;
  auto const *sDoc = dynamic_cast<SubDocument const *>(&doc);
  if (!sDoc) return true;
  if (&m_parser != &sDoc->m_parser) return true;
  return false;
}
}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
MultiplanParser::MultiplanParser(MWAWInputStreamPtr const &input, MWAWRSRCParserPtr const &rsrcParser, MWAWHeader *header)
  : MWAWSpreadsheetParser(input, rsrcParser, header)
  , m_state(new MultiplanParserInternal::State)
{
  setAsciiName("main-1");
  // reduce the margin (in case, the page is not defined)
  getPageSpan().setMargins(0.1);
}

MultiplanParser::~MultiplanParser()
{
}

////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void MultiplanParser::parse(librevenge::RVNGSpreadsheetInterface *docInterface)
{
  if (!getInput().get() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = true;
  try {
    // create the asciiFile
    ascii().setStream(getInput());
    ascii().open(asciiName());
    checkHeader(nullptr);
    ok = createZones();
    if (ok) {
      createDocument(docInterface);
      sendSpreadsheet();
    }
  }
  catch (...) {
    MWAW_DEBUG_MSG(("MultiplanParser::parse: exception catched when parsing\n"));
    ok = false;
  }

  ascii().reset();
  resetSpreadsheetListener();
  if (!ok) throw(libmwaw::ParseException());
}

////////////////////////////////////////////////////////////
// create the document
////////////////////////////////////////////////////////////
void MultiplanParser::createDocument(librevenge::RVNGSpreadsheetInterface *documentInterface)
{
  if (!documentInterface) return;
  if (getSpreadsheetListener()) {
    MWAW_DEBUG_MSG(("MultiplanParser::createDocument: listener already exist\n"));
    return;
  }

  // create the page list
  MWAWPageSpan ps(getPageSpan());
  ps.setPageSpan(1);
  for (int i=0; i<2; ++i) {
    if (!m_state->m_hfpEntries[i].valid()) continue;
    MWAWHeaderFooter header(i==0 ? MWAWHeaderFooter::HEADER :  MWAWHeaderFooter::FOOTER, MWAWHeaderFooter::ALL);
    header.m_subDocument.reset
    (new MultiplanParserInternal::SubDocument
     (*this, getInput(), m_state->m_hfpEntries[i]));
    ps.setHeaderFooter(header);
  }
  std::vector<MWAWPageSpan> pageList(1,ps);
  //
  MWAWSpreadsheetListenerPtr listen(new MWAWSpreadsheetListener(*getParserState(), pageList, documentInterface));
  setSpreadsheetListener(listen);
  listen->startDocument();
}


////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool MultiplanParser::createZones()
{
  if (!readPrinterMessage() || !readZoneB()) return false;
  if (!readColumnsPos() || !readPrinterInfo()) return false;
  if (!readHeaderFooter() || !readZoneC()) return false;
  if (!readZonesList()) return false;
  MWAWInputStreamPtr input = getInput();
  if (!input->isEnd()) {
    MWAW_DEBUG_MSG(("MultiplanParser::createZones: find extra data\n"));
    ascii().addPos(input->tell());
    ascii().addNote("Entries(Unknown):###extra");
  }
  return true;
}

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

////////////////////////////////////////////////////////////
// spreadsheet
////////////////////////////////////////////////////////////
bool MultiplanParser::readHeaderFooter()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+2*256)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readHeaderFooter: the zone seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  for (int i=0; i<2; ++i) {
    pos = input->tell();
    f.str("");
    f << "Entries(HF)[" << (i==0 ? "header" : "footer") << "]:";
    int sSz=int(input->readULong(1));
    m_state->m_hfpEntries[i].setBegin(pos+1);
    m_state->m_hfpEntries[i].setLength(sSz);
    std::string name;
    for (int c=0; c<sSz; ++c) name+=char(input->readULong(1));
    f << name;
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    input->seek(pos+256, librevenge::RVNG_SEEK_SET);
  }
  return true;
}

bool MultiplanParser::readPrinterInfo()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+0xbc)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readPrinterInfo: the zone seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(PrinterInfo):";
  int val=int(input->readULong(2));
  if (val!=0x7fff) f << "f0=" << val << ",";
  val=int(input->readULong(2));
  if (val) f << "f1=" << val << ",";
  f << "left[margin]=" << input->readULong(1) << ",";
  f << "width=" << input->readULong(1) << ",";
  f << "right[margin]=" << input->readULong(1) << ",";
  f << "length=" << input->readULong(1) << ",";
  // then 0 and a string?
  ascii().addDelimiter(input->tell(),'|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+130, librevenge::RVNG_SEEK_SET);

  pos=input->tell();
  f.str("");
  f << "PrinterInfo[II]:";
  f << "row[pbBreak]=[";
  for (int i=0; i<32; ++i) {
    val=int(input->readULong(1));
    if (!val) continue;
    for (int d=0, depl=1; d<8; ++d, depl<<=1) {
      if (val&depl) f << i*8+d << ",";
    }
  }
  f << "],";
  f << "col[pbBreak]=[";
  for (int i=0; i<8; ++i) {
    val=int(input->readULong(1));
    if (!val) continue;
    for (int d=0, depl=1; d<8; ++d, depl<<=1) {
      if (val&depl) f << i*8+d << ",";
    }
  }
  f << "],";
  for (int i=0; i<7; ++i) {
    val=int(input->readULong(2));
    int const expected[]= {0x48,0x48,0x36,0x36,1,1,0};
    if (val==expected[i]) continue;
    if (i==4) {
      if (val==0)
        f << "print[col,row,number]=no,";
      else
        f << "##print[col,row,number]=" << val << ",";
    }
    else
      f << "g" << i << "=" << val << ",";
  }
  m_state->m_font.setId(int(input->readULong(2)));
  m_state->m_font.setSize(float(input->readULong(2)));
  f << "font=[" << m_state->m_font.getDebugString(getFontConverter()) << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+58, librevenge::RVNG_SEEK_SET);
  return true;
}

bool MultiplanParser::readPrinterMessage()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+256)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readPrinterMessage: the zone seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(HF)[printerMessage]:";
  int sSz=int(input->readULong(1));
  m_state->m_hfpEntries[2].setBegin(pos+1);
  m_state->m_hfpEntries[2].setLength(sSz);
  std::string name;
  for (int c=0; c<sSz; ++c) name+=char(input->readULong(1));
  f << name;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+256, librevenge::RVNG_SEEK_SET);
  return true;
}

bool MultiplanParser::readColumnsPos()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+256)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readColumnsPos: the zone seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(ColPos):pos=[";
  for (int i=0; i<64; ++i) {
    m_state->m_columnPositions.push_back(int(input->readULong(2)));
    f << m_state->m_columnPositions.back() << ",";
  }
  f << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

bool MultiplanParser::readZonesList()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+20)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readZonesList: the zone seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(ZonesList):";
  int lastPos=0;
  f << "zones=[";
  for (int i=0, w=0; i<10; ++i) {
    int newPos=int(input->readULong(2));
    if (i==6) newPos+=lastPos; // length
    if (i==7) {
      lastPos=newPos;
      continue;
    }
    if (newPos>lastPos) {
      if (!input->checkPosition(pos+20+newPos)) {
        MWAW_DEBUG_MSG(("MultiplanParser::readZonesList: find a bad position"));
        f << "###";
      }
      else {
        m_state->m_entries[w].setBegin(pos+20+lastPos);
        m_state->m_entries[w].setEnd(pos+20+newPos);
      }
      f << std::hex << lastPos << "<->" << newPos << std::dec << ",";
      lastPos=newPos;
    }
    else
      f << "_,";
    ++w;
  }
  f << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  for (int i=0; i<9; ++i) {
    if (!m_state->m_entries[i].valid()) continue;
    bool ok=false;
    std::string name;
    switch (i) {
    case 1:
      ok=readZone1(m_state->m_entries[i]);
      break;
    case 3:
      ok=readCellDataPosition(m_state->m_entries[i]);
      break;
    case 4:
      name="Link";
      break;
    case 5:
      name="Link";
      break;
    case 6:
      name="DataCell";
      break;
    case 7: // the data are normally read in zone 6
      name="SharedData";
      break;
    case 8:
      name="Names";
      break;
    default:
      ok=false;
      break;
    }
    if (ok)
      continue;
    f.str("");
    if (!name.empty())
      f << "Entries(" << name << "):";
    else
      f << "Entries(Zone" << i << "):";
    ascii().addPos(m_state->m_entries[i].begin());
    ascii().addNote(f.str().c_str());
    ascii().addPos(m_state->m_entries[i].end());
    ascii().addNote("_");
    input->seek(m_state->m_entries[i].end(), librevenge::RVNG_SEEK_SET);
  }
  return true;
}

bool MultiplanParser::readZone1(MWAWEntry const &entry)
{
  if (entry.length()%30) {
    MWAW_DEBUG_MSG(("MultiplanParser::readZone1: the zone size seems bad\n"));
    return false;
  }
  MWAWInputStreamPtr input = getInput();
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "Entries(Zone1):";
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  int N=int(entry.length()/30);
  for (int i=0; i<N; ++i) {
    /* find something like that
       0000000000fb01d80012001c00fb01d800000000000d0007ffe4ffeec000
       00000000005500550012001c005500550000000000040001ffe4ffeec000
    */
    long pos=input->tell();
    f.str("");
    f << "Zone1-" << i << ":";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    input->seek(pos+30, librevenge::RVNG_SEEK_SET);
  }
  return true;
}

bool MultiplanParser::readCellDataPosition(MWAWEntry const &entry)
{
  if (m_state->m_maximumCell[0]<=0 || m_state->m_maximumCell[1]<=0 || entry.length()/m_state->m_maximumCell[0]/2<m_state->m_maximumCell[1]) {
    MWAW_DEBUG_MSG(("MultiplanParser::readCellDataPosition: the zone seems bad\n"));
    return false;
  }
  MWAWInputStreamPtr input = getInput();
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "Entries(DataPos):";
  m_state->m_cellPositions.resize(size_t(m_state->m_maximumCell[0]));
  auto &posSet=m_state->m_cellPositionsSet;
  for (int i=0; i<m_state->m_maximumCell[0]; ++i) {
    f << "[" << std::hex;
    auto &cellPos=m_state->m_cellPositions[size_t(i)];
    for (int j=0; j<m_state->m_maximumCell[1]; ++j) {
      cellPos.push_back(int(input->readLong(2)));
      posSet.insert(cellPos.back());
      if (cellPos.back())
        f << cellPos.back() << ",";
      else
        f << "_,";
    }
    f << std::dec << "],";
  }
  if (input->tell()!=entry.end()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readCellDataPosition: find extra data\n"));
    f << "###extra";
    ascii().addDelimiter(input->tell(),'|');
  }
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  return true;
}

bool MultiplanParser::readLink(int pos, MWAWCellContent::FormulaInstruction &instr)
{
  auto it=m_state->m_posToLinkMap.find(pos);
  if (it!=m_state->m_posToLinkMap.end()) {
    instr=it->second;
    return true;
  }
  auto const &entry=m_state->m_entries[4];
  if (!entry.valid() || pos<0 || pos+12>entry.length()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readLink: the pos %d seems bad\n", pos));
    return false;
  }
  auto input = getInput();
  long actPos=input->tell();
  long begPos=entry.begin()+pos;
  input->seek(begPos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "Link-" << std::hex << pos << std::dec << "[pos]:";
  int dSz=int(input->readULong(1));
  if (dSz<0 || pos+12+dSz > entry.end()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readLink: the pos %d seems bad\n", pos));
    input->seek(actPos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  int type=int(input->readULong(1));
  f << "type=" << type << ",";
  int lPos=int(input->readULong(2));
  if (!readLinkFilename(lPos, instr))
    f << "###";
  f << "pos=" << std::hex << lPos << std::dec << ",";
  int val;
  for (int j=0; j<2; ++j) {
    val=int(input->readULong(1));
    int const expected[]= {0x1a,0x1a};
    if (val!=expected[j]) f << "f" << j+2 << "=" << val << ",";
  }
  for (int j=0; j<3; ++j) {  // f4=1|821
    val=int(input->readULong(2));
    if (val)
      f << "f" << j+4 << "=" << std::hex << val << std::dec << ",";
  }
  bool ok=false;
  switch (type) {
  case 0: {
    ok=true;
    auto fontConverter=getFontConverter();
    auto const fId=m_state->m_font.id();
    librevenge::RVNGString name=instr.m_fileName.c_str();
    name.append(':');
    for (int i=0; i<dSz; ++i) {
      auto ch=static_cast<unsigned char>(input->readULong(1));
      int unicode = fontConverter->unicode(fId, static_cast<unsigned char>(ch));
      if (unicode!=-1)
        libmwaw::appendUnicode(uint32_t(unicode), name);
      else if (ch==0x9 || ch > 0x1f)
        libmwaw::appendUnicode(static_cast<uint32_t>(ch), name);
      else {
        f << "##";
        MWAW_DEBUG_MSG(("MultiplanParser::readLink: name seems bad\n"));
      }
    }
    instr.m_type=instr.F_Text;
    instr.m_content=name.cstr();
    break;
  }
  case 1: {
    if (dSz<4)
      break;
    ok=true;
    int rows[2], cols[2];
    for (auto &r : rows) r=int(input->readULong(1));
    for (auto &c : cols) c=int(input->readULong(1));
    for (int j=0; j<2; ++j) {
      instr.m_position[j]=MWAWVec2i(cols[j],rows[j]);
      instr.m_positionRelative[j]=MWAWVec2b(false,false);
    }
    instr.m_type=instr.m_position[0]==instr.m_position[1] ? instr.F_Cell : instr.F_CellList;
    f << instr << ",";
    break;
  }
  default:
    MWAW_DEBUG_MSG(("MultiplanParser::readLink: find unknown type %d\n", type));
    break;
  }
  if (!ok) {
    MWAW_DEBUG_MSG(("MultiplanParser::readLink: can not read link at pos %d\n", pos));
    f << "###";
  }
  else
    m_state->m_posToLinkMap[pos]=instr;
  ascii().addPos(begPos);
  ascii().addNote(f.str().c_str());
  input->seek(actPos, librevenge::RVNG_SEEK_SET);
  return ok;
}

bool MultiplanParser::readLinkFilename(int pos, MWAWCellContent::FormulaInstruction &instr)
{
  auto const &entry=m_state->m_entries[5];
  if (!entry.valid() || pos<0 || pos+10>entry.length()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readLinkFilename: the pos %d seems bad\n", pos));
    return false;
  }
  MWAWInputStreamPtr input = getInput();
  long actPos=input->tell();
  long begPos=entry.begin()+pos;
  input->seek(begPos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "Link-" << std::hex << pos << std::dec << ":";
  for (int i=0; i<2; ++i) {
    int val=int(input->readLong(2));
    if (val!=1-i) f << "f" << i << "=" << val << ",";
  }
  f << "unkn=" << std::hex << input->readULong(4) << std::dec << ","; // d66aa996 | d66aab1e maybe dirId, fileId
  int dSz=int(input->readULong(1));
  if (begPos+9+dSz>entry.end()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readLinkFilename: the pos %d seems bad\n", pos));
    input->seek(actPos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  librevenge::RVNGString filename;
  auto fontConverter=getFontConverter();
  auto const fId=m_state->m_font.id();
  for (int i=0; i<dSz; ++i) {
    auto ch=static_cast<unsigned char>(input->readULong(1));
    int unicode = fontConverter->unicode(fId, static_cast<unsigned char>(ch));
    if (unicode!=-1)
      libmwaw::appendUnicode(uint32_t(unicode), filename);
    else if (ch==0x9 || ch > 0x1f)
      libmwaw::appendUnicode(static_cast<uint32_t>(ch), filename);
    else {
      f << "##";
      MWAW_DEBUG_MSG(("MultiplanParser::readLinkFilename: dir seems bad\n"));
    }
  }
  instr.m_fileName=filename.cstr();
  f << instr.m_fileName << ",";
  instr.m_sheet="Sheet0";
  ascii().addPos(begPos);
  ascii().addNote(f.str().c_str());
  input->seek(actPos, librevenge::RVNG_SEEK_SET);
  return true;
}

bool MultiplanParser::readSharedData(int pos, int cellType, MWAWVec2i const &cellPos, MWAWCellContent &content)
{
  auto const &entry=m_state->m_entries[7];
  if (!entry.valid() || pos<0 || pos+3>entry.length()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readSharedData: the pos %d seems bad\n", pos));
    return false;
  }
  MWAWInputStreamPtr input = getInput();
  long actPos=input->tell();
  long begPos=entry.begin()+pos;
  input->seek(begPos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "SharedData-" << std::hex << pos << std::dec << ":";
  int type=int(input->readULong(2));
  f << "type=" << (type&3) << ",";
  int N=(type/4);
  if (N!=2) f << "used=" << N << ",";
  int dSz=int(input->readULong(1));
  long endPos=begPos+3+dSz;
  if (endPos>entry.end()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readSharedData: the pos %d seems bad\n", pos));
    input->seek(actPos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  bool ok=true;
  switch (type&3) {
  case 0:
    switch (cellType&3) {
    case 0: {
      double value;
      if (dSz!=8 || !readDouble(value))
        ok=false;
      else {
        content.m_contentType=content.C_NUMBER;
        content.setValue(value);
        f << value << ",";
      }
      break;
    }
    case 1: {
      content.m_contentType=content.C_TEXT;
      content.m_textEntry.setBegin(input->tell());
      content.m_textEntry.setLength(dSz);
      std::string name;
      for (int c=0; c<dSz; ++c) name+=char(input->readULong(1));
      f << name << ",";
      break;
    }
    case 2:
      if (dSz!=8)
        ok=false;
      else {
        f << "Nan" << input->readULong(1) << ",";
        input->seek(7, librevenge::RVNG_SEEK_CUR);
        content.m_contentType=content.C_NUMBER;
        content.setValue(std::nan(""));
      }
      break;
    case 3:
    default:
      if (dSz!=8)
        ok=false;
      else {
        int val=int(input->readULong(1));
        content.m_contentType=content.C_NUMBER;
        content.setValue(val);
        if (val==0)
          f << "false,";
        else if (val==1)
          f << "true,";
        else
          f << "##bool=" << val << ",";
        input->seek(7, librevenge::RVNG_SEEK_CUR);
      }
      break;
    }
    break;
  case 1: {
    std::string err;
    if (!readFormula(cellPos, content.m_formula, endPos, err))
      f << "###";
    else
      content.m_contentType=content.C_FORMULA;
    for (auto const &fo : content.m_formula) f << fo;
    f << ",";
    f << err;
    break;
  }
  default:
    ok=false;
    break;
  }
  if (!ok) {
    MWAW_DEBUG_MSG(("MultiplanParser::readSharedData: can not read data for the pos %d\n", pos));
    f << "###";
  }
  if (m_state->m_posToSharedDataSeen.find(pos)==m_state->m_posToSharedDataSeen.end()) {
    m_state->m_posToSharedDataSeen.insert(pos);
    if (input->tell()!=endPos)
      ascii().addDelimiter(input->tell(),'|');
    ascii().addPos(begPos);
    ascii().addNote(f.str().c_str());
  }
  input->seek(actPos, librevenge::RVNG_SEEK_SET);
  return true;
}

bool MultiplanParser::readName(int pos, MWAWCellContent::FormulaInstruction &instruction)
{
  auto it=m_state->m_posToNameMap.find(pos);
  if (it!=m_state->m_posToNameMap.end()) {
    instruction=it->second;
    return true;
  }
  auto const &entry=m_state->m_entries[8]; // the named entry
  if (!entry.valid() || pos<0 || pos+10>=entry.length()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readName: the pos %d seeems bad\n", pos));
    return false;
  }
  MWAWInputStreamPtr input = getInput();
  long actPos=input->tell();
  long begPos=entry.begin()+pos;
  input->seek(begPos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "Names-" << std::hex << pos << std::dec << ":";
  int val=int(input->readULong(1));
  int dSz=(val>>3);
  if (dSz<=0 || begPos+10+dSz>entry.end()) {
    input->seek(actPos, librevenge::RVNG_SEEK_SET);
    MWAW_DEBUG_MSG(("MultiplanParser::readName: the pos %d seeems bad\n", pos));
    return false;
  }
  if (val&3) f << "f0=" << val << ",";
  val=int(input->readULong(1)); // 40|60
  if (val) f << "f1=" << std::hex << val << std::dec << ",";
  int rows[2];
  for (auto &r : rows) r=int(input->readULong(1));
  val=int(input->readULong(2));
  int cols[2]= {(val>>10),(val>>4)&0x3f};
  for (int i=0; i<2; ++i) {
    instruction.m_position[i]=MWAWVec2i(cols[i], rows[i]);
    instruction.m_positionRelative[i]=MWAWVec2b(false,false);
  }
  instruction.m_type=instruction.m_position[0]==instruction.m_position[1] ? instruction.F_Cell : instruction.F_CellList;
  f << instruction << ",";
  m_state->m_posToNameMap[pos]=instruction;
  if (val&0xf) f << "f2=" << (val&0xf) << ","; // 0|2
  for (int i=0; i<2; ++i) { // 0
    val=int(input->readLong(2));
    if (val) f << "f" << i+2 << "=" << val << ",";
  }
  std::string name;
  for (int c=0; c<dSz; ++c) name+=char(input->readULong(1));
  f << name << ",";
  ascii().addPos(begPos);
  ascii().addNote(f.str().c_str());

  input->seek(actPos, librevenge::RVNG_SEEK_SET);
  return true;
}

bool MultiplanParser::readZoneB()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+82)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readZoneB: the zone seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(ZoneB):";
  int dim[2];
  for (auto &d : dim) d=int(input->readULong(2));
  m_state->m_maximumCell=MWAWVec2i(dim[0],dim[1]);
  f << "cell[max]=" << m_state->m_maximumCell << ",";
  int val;
  for (int i=0; i<7; ++i) {
    val=int(input->readLong(2));
    int const expected[]= {0,0,0x7fff,0x47,0xc,0x1e7,0x10a};
    if (val!=expected[i])
      f << "f" << i << "=" << val << ",";
  }
  for (int i=0; i<15; ++i) {
    val=int(input->readLong(2));
    if (!val) continue;
    f << "g" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  pos=input->tell();
  f.str("");
  f << "ZoneB[II]:";
  for (int i=0; i<2; ++i) {
    val=int(input->readLong(1));
    if (val!=1-i) f << "f" << i << "=" << val << ",";
  }
  int dim4[4];
  for (auto &d : dim4) d=int(input->readULong(1));
  f << "selection=" << MWAWBox2i(MWAWVec2i(dim4[0],dim4[1]),MWAWVec2i(dim4[2],dim4[3])) << ",";
  for (int i=0; i<19; ++i) { // 0
    val=int(input->readLong(1));
    if (val) f << "g" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  input->seek(pos+82, librevenge::RVNG_SEEK_SET);
  return true;
}

bool MultiplanParser::readZoneC()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+22)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readZoneC: the zone seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(ZoneC):";
  int val;
  f << "unkn=[";
  for (int i=0; i<4; ++i) { // small number
    val=int(input->readLong(2));
    if (val)
      f << val << ",";
    else
      f << "_,";
  }
  f << "],";
  val=int(input->readLong(2));
  if (val==1)
    f << "protected,";
  else if (val)
    f << "protected=#" << val << ",";
  val=int(input->readULong(2));
  if (val) f << "passwd[crypted]=" << std::hex << val << std::dec << ",";
  for (int i=0; i<5; ++i) {
    val=int(input->readLong(2));
    int const expected[]= {0,0,0,2,1};
    if (val!=expected[i]) f << "g" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// double
////////////////////////////////////////////////////////////
bool MultiplanParser::readDouble(double &value)
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  value=0;
  if (!input->checkPosition(input->tell()+8)) {
    MWAW_DEBUG_MSG(("MultiplanParser::readDouble: the zone is too short\n"));
    return false;
  }
  int exponant=int(input->readULong(1));
  double sign=1;
  if (exponant&0x80) {
    exponant&=0x7f;
    sign=-1;
  }
  bool ok=true;
  double factor=1;
  for (int i=0; i<7; ++i) {
    int val=int(input->readULong(1));
    for (int d=0; d<2; ++d) {
      int v= d==0 ? (val>>4) : (val&0xf);
      if (v>=10) {
        MWAW_DEBUG_MSG(("MultiplanParser::readDouble: oops find a bad digits\n"));
        ok=false;
        break;
      }
      factor/=10.;
      value+=factor*v;
    }
    if (!ok) break;
  }
  value *= sign*std::pow(10.,exponant-0x40);
  input->seek(pos+8, librevenge::RVNG_SEEK_SET);
  return ok;
}

////////////////////////////////////////////////////////////
// formula
////////////////////////////////////////////////////////////
namespace MultiplanParserInternal
{
struct Functions {
  char const *m_name;
  int m_arity;
};

static Functions const s_listOperators[] = {
  // 0
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  // 1
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  // 2
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { ":", 2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { ":", 2}, { "", -2}, { "", -2},
  // 3
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  // 4
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { ":", 2}, { "", -2}, { "", -2},
  // 5
  { "&", 2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  // 6
  { "<", 2}, { "", -2}, { "<=", 2}, { "", -2},
  { "=", 2}, { "", -2}, { ">=", 2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  // 7
  { ">", 2}, { "", -2}, { "<>", 2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  // 8
  { "", -2}, { "", -2}, { "+", 2}, { "", -2},
  { "-", 2}, { "", -2}, { "*", 2}, { "", -2},
  { "/", 2}, { "", -2}, { "^", 2}, { "", -2},
  { "", -2}, { "", -2}, { "-", 1}, { "", -2},
  // 9
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
  { "%", 1}, { "", -2}, { "", -2}, { "", -2},
  { "", -2}, { "", -2}, { "", -2}, { "", -2},
};

char const *s_listFunctions[]= {
  // 0
  "Count", "If", "IsNA", "IsError",
  "Sum", "Average", "Min", "Max",
  "Row", "Column", "NA", "NPV",
  "Stdev", "Dollar", "Fixed", "Sin",
  // 1
  "Cos", "Tan", "Atan", "Pi",
  "Sqrt", "Exp", "Ln", "Log",
  "Abs", "Int", "Sign", "Round",
  "Lookup", "Index", "Rept", "Mid",
  // 2
  "Length", "Value", "True", "False",
  "And", "Or", "Not", "Mod",
  "IterCnt", "Delta", "PV", "FV",
  "NPer", "PMT", "Rate", "MIRR",
  // 3
  "Irr", nullptr, nullptr, nullptr,
  nullptr, nullptr, nullptr, nullptr,
  nullptr, nullptr, nullptr, nullptr,
  nullptr, nullptr, nullptr, nullptr,
};
}

bool MultiplanParser::readFormula(MWAWVec2i const &cellPos, std::vector<MWAWCellContent::FormulaInstruction> &formula, long endPos, std::string &error)
{
  formula.clear();
  MWAWInputStreamPtr input = getInput();
  std::vector<std::vector<MWAWCellContent::FormulaInstruction> > stack;
  auto const &listOperators=MultiplanParserInternal::s_listOperators;
  int const numOperators=int(MWAW_N_ELEMENTS(listOperators));
  auto const &listFunctions=MultiplanParserInternal::s_listFunctions;
  int const numFunctions=int(MWAW_N_ELEMENTS(listFunctions));
  bool ok=true;
  int closeDelayed=0;
  bool checkForClose=false;
  while (input->tell()<=endPos) {
    long pos=input->tell();
    int wh=pos==endPos ? -1 : int(input->readULong(1));
    bool needCloseParenthesis=closeDelayed && (checkForClose || pos==endPos);
    ok=true;
    if (closeDelayed && !needCloseParenthesis && wh!=0x3c)
      needCloseParenthesis=wh>=numOperators || listOperators[wh].m_arity!=2;
    while (needCloseParenthesis && closeDelayed>0) {
      auto len=stack.size();
      if (len<2) {
        error="##closedParenthesis,";
        ok=false;
        break;
      }
      auto &dParenthesisFunc=stack[len-2];
      if (dParenthesisFunc.size()!=1 || dParenthesisFunc[0].m_type!=dParenthesisFunc[0].F_Operator ||
          dParenthesisFunc[0].m_content!="(") {
        error="##closedParenthesis,";
        ok=false;
        break;
      }
      dParenthesisFunc.insert(dParenthesisFunc.end(),stack.back().begin(), stack.back().end());
      MWAWCellContent::FormulaInstruction instr;
      instr.m_type=instr.F_Operator;
      instr.m_content=")";
      dParenthesisFunc.push_back(instr);
      stack.resize(len-1);
      --closeDelayed;
    }
    if (!ok || pos==endPos)
      break;
    int arity=0;
    MWAWCellContent::FormulaInstruction instr;
    ok=false;
    bool noneInstr=false, closeFunction=false;
    switch (wh) {
    case 0:
      if (pos+3>endPos || !readLink(int(input->readULong(2)), instr))
        break;
      ok=true;
      break;
    case 0x12: {
      if (pos+2>endPos)
        break;
      ok=true;
      instr.m_type=instr.F_Function;
      int id=int(input->readULong(1));
      if (id<numFunctions && listFunctions[id])
        instr.m_content=listFunctions[id];
      else {
        std::stringstream s;
        s << "Funct" << std::hex << id << std::dec;
        instr.m_content=s.str();
      }
      std::vector<MWAWCellContent::FormulaInstruction> child;
      child.push_back(instr);
      stack.push_back(child);
      instr.m_type=instr.F_Operator;
      instr.m_content="(";
      break;
    }
    case 0x51:
    case 0x71:
    case 0x91:
    case 0xd1:
    case 0xf1:
      closeFunction=ok=true;
      break;
    case 0x1c: // use before %
    case 0x1e: // use for <> A 1e B "code <>"
    case 0x34: // use for <=,>= ... A 34 B "code <=,..."
    case 0x36: // use before -unary
      noneInstr=ok=true;
      break;
    case 0x3a:
      ok=true;
      instr.m_type=instr.F_Operator;
      instr.m_content=";";
      break;
    case 0x3c:
      noneInstr=ok=true;
      ++closeDelayed;
      break;
    case 0x3e:
      ok=true;
      instr.m_type=instr.F_Operator;
      instr.m_content="(";
      break;
    case 0x56: {
      int dSz=int(input->readULong(1));
      if (pos+2+dSz>endPos)
        break;
      instr.m_type=instr.F_Text;
      auto fontConverter=getFontConverter();
      auto const fId=m_state->m_font.id();
      librevenge::RVNGString content;
      for (int i=0; i<dSz; ++i) {
        auto ch=static_cast<unsigned char>(input->readULong(1));
        int unicode = fontConverter->unicode(fId, static_cast<unsigned char>(ch));
        if (unicode!=-1)
          libmwaw::appendUnicode(uint32_t(unicode), content);
        else if (ch==0x9 || ch > 0x1f)
          libmwaw::appendUnicode(static_cast<uint32_t>(ch), content);
        else {
          MWAW_DEBUG_MSG(("MultiplanParser::readFormula: content seen bad seems bad\n"));
          error="##content";
        }
      }
      instr.m_content=content.cstr();
      ok=true;
      break;
    }
    case 0x21: // double
    case 0xe1:
    case 0x8f: // simple
    case 0xef:
      if (pos+3>endPos)
        break;
      instr.m_type=instr.F_Cell;
      instr.m_positionRelative[0]=MWAWVec2b(false,false);
      instr.m_position[0][1]=int(input->readULong(1));
      instr.m_position[0][0]=int(input->readULong(1));
      ok=(instr.m_position[0][0]<63) && (instr.m_position[0][1]<255);
      if (!ok) {
        error="###RorC";
        MWAW_DEBUG_MSG(("MultiplanParser::readFormula: find only row/column reference\n"));
      }
      break;
    case 0x29: // example C2 R1
      MWAW_DEBUG_MSG(("MultiplanParser::readFormula: find union operator\n"));
      error="###union";
      ok=false;
      break;
    case 0x37: // use for list cell
    case 0x53:
    case 0x73:
    case 0x93: // basic cell
    case 0xf3: { // difference ?
      if (pos+3>endPos)
        break;
      instr.m_type=instr.F_Cell;
      instr.m_positionRelative[0]=MWAWVec2b(true,true);
      int val=int(input->readULong(2));
      auto &newPos=instr.m_position[0];
      if (val&0x80)
        newPos[1]=cellPos[1]-(val>>8);
      else
        newPos[1]=cellPos[1]+(val>>8);
      if (val&0x40)
        newPos[0]=cellPos[0]-(val&0x3f);
      else
        newPos[0]=cellPos[0]+(val&0x3f);
      ok=newPos[0]>=0 && newPos[1]>=0;
      break;
    }
    case 0x94:
      if (pos+9>endPos || !readDouble(instr.m_doubleValue))
        break;
      instr.m_type=instr.F_Double;
      ok=true;
      break;
    case 0xf5:
      if (pos+3>endPos || !readName(int(input->readULong(2)), instr))
        break;
      ok=true;
      break;
    default:
      if (wh<numOperators && listOperators[wh].m_arity!=-2) {
        instr.m_content=listOperators[wh].m_name;
        instr.m_type=instr.F_Function;
        arity=listOperators[wh].m_arity;
      }
      if (instr.m_content.empty()) {
        MWAW_DEBUG_MSG(("MultiplanParser::readFormula: find unknown type %x\n", wh));
        std::stringstream s;
        s << "##unkn[func]=" << std::hex << wh << std::dec << ",";
        error=s.str();
        break;
      }
      ok=true;
      break;
    }
    if (!ok) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    checkForClose=!noneInstr && closeDelayed>0;
    if (noneInstr) continue;
    if (closeFunction) {
      ok=false;
      if (stack.empty()) {
        error="##closed,";
        break;
      }
      auto it=stack.end();
      --it;
      for (; it!=stack.begin(); --it) {
        if (it->size()!=1) continue;
        auto const &dInstr=(*it)[0];
        if (dInstr.m_type!=dInstr.F_Operator || dInstr.m_content!="(") continue;
        auto fIt=it;
        --fIt;
        auto &functionStack=*fIt;
        if (functionStack.size()!=1 || functionStack[0].m_type!=functionStack[0].F_Function) continue;
        ok=true;
        for (; it!=stack.end(); ++it)
          functionStack.insert(functionStack.end(), it->begin(), it->end());
        ++fIt;
        stack.erase(fIt, stack.end());
        break;
      }
      if (!ok) {
        error="##closed";
        break;
      }
      instr.m_type=instr.F_Operator;
      instr.m_content=")";
      stack.back().push_back(instr);
      continue;
    }
    std::vector<MWAWCellContent::FormulaInstruction> child;
    if (instr.m_type!=MWAWCellContent::FormulaInstruction::F_Function) {
      child.push_back(instr);
      stack.push_back(child);
      continue;
    }
    size_t numElt = stack.size();
    if (static_cast<int>(numElt) < arity) {
      std::stringstream s;
      s << instr.m_content << "[##" << arity << "]";
      error=s.str();
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=false;
      break;
    }
    if (arity==1) {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      if (instr.m_content=="%")
        stack[numElt-1].push_back(instr);
      else
        stack[numElt-1].insert(stack[numElt-1].begin(), instr);
      continue;
    }
    if (arity==2) {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      stack[numElt-2].push_back(instr);
      stack[numElt-2].insert(stack[numElt-2].end(), stack[numElt-1].begin(), stack[numElt-1].end());
      stack.resize(numElt-1);
      continue;
    }
    ok=false;
    error = "### unexpected arity";
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    break;
  }
  long pos=input->tell();
  if (pos!=endPos || !ok || closeDelayed || stack.size()!=1 || stack[0].empty()) {
    MWAW_DEBUG_MSG(("MultiplanParser::readFormula: can not read a formula\n"));
    ascii().addDelimiter(pos, '|');
    input->seek(endPos, librevenge::RVNG_SEEK_SET);

    std::stringstream s;
    if (!error.empty())
      s << error;
    else
      s << "##unknownError";
    s << "[";
    for (auto const &i : stack) {
      for (auto const &j : i)
        s << j << ",";
    }
    s << "],";
    error=s.str();
    return true;
  }
  formula=stack[0];
  return true;
}

////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool MultiplanParser::checkHeader(MWAWHeader *header, bool strict)
{
  *m_state = MultiplanParserInternal::State();

  MWAWInputStreamPtr input = getInput();
  if (!input || !input->hasDataFork())
    return false;

  if (!input->checkPosition(0x778)) {
    MWAW_DEBUG_MSG(("MultiplanParser::checkHeader: file is too short\n"));
    return false;
  }
  input->seek(0,librevenge::RVNG_SEEK_SET);
  if (input->readULong(2)!=0x11ab || input->readULong(2)!=0 ||
      input->readULong(2)!=0x13e8 || input->readULong(2)!=0)
    return false;
  libmwaw::DebugStream f;
  f << "FileHeader:";
  for (int i=0; i<2; ++i) {
    int val=int(input->readLong(2));
    if (val)
      f << "f" << i << "=" << val << ",";
  }
  ascii().addPos(0);
  ascii().addNote(f.str().c_str());
  if (strict) {
    // read the last zone list position and check that it corresponds to a valid position
    input->seek(0x758, librevenge::RVNG_SEEK_SET);
    int val=int(input->readULong(2));
    if (val<0x3c || !input->checkPosition(0x75a+val)) {
      MWAW_DEBUG_MSG(("MultiplanParser::checkHeader: can not find last spreadsheet position\n"));
      return false;
    }
  }
  /* checkme: MsMultiplan DOS begins by a list of 8 potential filename
     linked to this files (with length 0x1f) maybe something like
     that... */
  input->seek(0x30,librevenge::RVNG_SEEK_SET);
  ascii().addPos(0x30);
  ascii().addNote("Entries(ZoneA):");
  for (int i=0; i<4; ++i) {
    long pos=input->tell();
    f.str("");
    f << "ZoneA" << i << ":";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    input->seek(pos+0x80,librevenge::RVNG_SEEK_SET);
  }
  long pos=input->tell();
  ascii().addPos(pos);
  ascii().addNote("ZoneA4");
  input->seek(0x272,librevenge::RVNG_SEEK_SET);
  if (header)
    header->reset(MWAWDocument::MWAW_T_MICROSOFTMULTIPLAN, 1, MWAWDocument::MWAW_K_SPREADSHEET);
  return true;
}

////////////////////////////////////////////////////////////
// send spreadsheet
////////////////////////////////////////////////////////////
bool MultiplanParser::sendText(MWAWEntry const &entry)
{
  auto listener=getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("MultiplanParser::sendText: can not find the listener\n"));
    return false;
  }
  listener->setFont(m_state->m_font);
  auto input=getInput();
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  for (long l=0; l<entry.length(); ++l) {
    if (input->isEnd()) {
      MWAW_DEBUG_MSG(("MultiplanParser::sendText: oops, can not read a character\n"));
      break;
    }
    auto c=static_cast<unsigned char>(input->readULong(1));
    if (c==0x9)
      listener->insertTab();
    else if (c==0xa || c==0xd)
      listener->insertEOL();
    else
      listener->insertCharacter(c);
  }
  return true;
}

bool MultiplanParser::sendCell(MWAWVec2i const &cellPos, int p)
{
  MWAWSpreadsheetListenerPtr listener=getSpreadsheetListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("MultiplanParser::sendCell: I can not find the listener\n"));
    return false;
  }
  auto const &entry=m_state->m_entries[6];
  if (p<=0 || p>entry.length()) {
    MWAW_DEBUG_MSG(("MultiplanParser::sendCell: unexpected position %d\n", p));
    return false;
  }
  MWAWCell cell;
  MWAWCellContent content;
  MWAWCell::Format format;
  cell.setPosition(cellPos);
  cell.setFont(m_state->m_font);
  libmwaw::DebugStream f;
  f << "DataCell[C" << cellPos[0]+1 << "R" << cellPos[1]+1 << "]:";
  long pos=entry.begin()+p;
  auto it=m_state->m_cellPositionsSet.find(p);
  ++it;
  long endPos=it!=m_state->m_cellPositionsSet.end() ? entry.begin()+*it : entry.end();
  if (endPos-pos<4) {
    MWAW_DEBUG_MSG(("MultiplanParser::sendCell: a cell %d seems to short\n", p));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  auto input=getInput();
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  int formSize=int(input->readULong(1));
  if (formSize) f << "form[size]=" << std::hex << formSize << std::dec << ",";
  int val=int(input->readULong(1));
  int digits=(val&0xf);
  if (digits) f << "decimal=" << digits << ",";
  int form=(val>>4)&7;
  format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
  switch (form) {
  case 2:
    format.m_numberFormat=MWAWCell::F_NUMBER_SCIENTIFIC;
    format.m_digits=digits;
    f << "scientific,";
    break;
  case 3:
    format.m_numberFormat=MWAWCell::F_NUMBER_DECIMAL;
    format.m_digits=digits;
    f << "decimal,";
    break;
  case 4: // default
    break;
  case 5:
    format.m_numberFormat=MWAWCell::F_NUMBER_CURRENCY;
    format.m_digits=digits;
    f << "currency,";
    break;
  case 6: // a bar
    f << "bar,";
    break;
  case 7:
    format.m_numberFormat=MWAWCell::F_NUMBER_PERCENT;
    format.m_digits=digits;
    f << "percent,";
    break;
  default:
    f << "format=" << form << ",";
    break;
  }
  cell.setProtected((val&0x80)!=0);
  if ((val&0x80)==0) f << "no[protection],";
  val=int(input->readULong(1));
  int align=(val>>2)&7;
  switch (align) {
  case 1:
    cell.setHAlignment(cell.HALIGN_CENTER);
    f << "center,";
    break;
  case 0: // default
  case 2: // generic
    break;
  case 3:
    cell.setHAlignment(cell.HALIGN_LEFT);
    f << "left,";
    break;
  case 4:
    cell.setHAlignment(cell.HALIGN_RIGHT);
    f << "right,";
    break;
  default:
    f << "#align=" << (align) << ",";
    break;
  }
  switch (val&3) {
  case 0:
    f << "double,";
    format.m_format=MWAWCell::F_NUMBER;
    content.m_contentType=content.C_NUMBER;
    break;
  case 1:
    format.m_format=MWAWCell::F_TEXT;
    content.m_contentType=content.C_TEXT;
    f << "text,";
    break;
  case 2:
    format.m_format=MWAWCell::F_NUMBER;
    content.m_contentType=content.C_NUMBER;
    f << "nan,";
    break;
  case 3: // or nothing
    format.m_format=MWAWCell::F_BOOLEAN;
    content.m_contentType=content.C_NUMBER;
    f << "bool,";
    break;
  default: // impossible
    break;
  }
  cell.setFormat(format);
  if ((val&0x20)==0)
    f << "no20[f1],";
  if (val&0x40)
    f << "shared,";
  int type=(val&0xe3);
  if (val&0x80) f << "80[f1],";
  int dSz=int(input->readULong(1));
  if (endPos<pos+4+dSz) {
    MWAW_DEBUG_MSG(("MultiplanParser::sendCell: a cell seems to short\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  if ((type&0x3)==0 && dSz==8) {
    double value;
    if (!readDouble(value))
      f << "###";
    else
      content.setValue(value);
    f << value << ",";
  }
  else if ((type&0x3)==1 && dSz && pos+4+dSz+((type&0x40) ? 2 : 0) <= endPos) {
    content.m_textEntry.setBegin(input->tell());
    content.m_textEntry.setLength(dSz);
    std::string name;
    for (int c=0; c<dSz; ++c) name+=char(input->readULong(1));
    f << name << ",";
  }
  else if ((type&0x3)==2 && dSz==8) {
    content.setValue(std::nan(""));
    f << "Nan" << input->readULong(1) << ",";
    input->seek(7, librevenge::RVNG_SEEK_CUR);
  }
  else if ((type&0x3)==3 && dSz==8) {
    val=int(input->readULong(1));
    content.setValue(val);
    if (val==0)
      f << "false,";
    else if (val==1)
      f << "true,";
    else
      f << "##bool=" << val << ",";
    input->seek(7, librevenge::RVNG_SEEK_CUR);
  }
  if ((type&0x40) && input->tell()+2<=endPos && (formSize==0 || formSize==2)) {
    if ((input->tell()-pos)%2)
      input->seek(1, librevenge::RVNG_SEEK_CUR);
    int nPos=int(input->readULong(2));
    if (!readSharedData(nPos, type, cellPos, content))
      f << "###";
    f << "sharedData-" << std::hex << nPos << std::dec << ",";
  }
  else if (!(type&0x40) && formSize && input->tell()+formSize<=endPos) {
    auto endFPos=input->tell()+formSize;
    std::string err;
    if (!readFormula(cellPos, content.m_formula, endFPos, err)) {
      ascii().addDelimiter(input->tell(),'|');
      f << "###";
    }
    else
      content.m_contentType=content.C_FORMULA;

    for (auto const &fo : content.m_formula) f << fo;
    f << ",";
    f << err;
    input->seek(endFPos, librevenge::RVNG_SEEK_SET);
  }
  else if (formSize) {
    MWAW_DEBUG_MSG(("MultiplanParser::sendCell: can not read a formula\n"));
    f << "###form";
  }
  listener->openSheetCell(cell, content);
  if (content.m_textEntry.valid()) {
    listener->setFont(cell.getFont());
    input->seek(content.m_textEntry.begin(), librevenge::RVNG_SEEK_SET);
    while (!input->isEnd() && input->tell()<content.m_textEntry.end()) {
      auto c=static_cast<unsigned char>(input->readULong(1));
      if (c==0x9)
        listener->insertTab();
      else if (c==0xa || c==0xd)
        listener->insertEOL();
      else
        listener->insertCharacter(c);
    }
  }
  listener->closeSheetCell();
  if (input->tell()!=endPos)
    ascii().addDelimiter(input->tell(),'|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

bool MultiplanParser::sendSpreadsheet()
{
  MWAWSpreadsheetListenerPtr listener=getSpreadsheetListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("MultiplanParser::sendSpreadsheet: I can not find the listener\n"));
    return false;
  }
  listener->openSheet(m_state->getColumnsWidth(), librevenge::RVNG_POINT, std::vector<int>(), "Sheet0");
  auto const &dataEntry=m_state->m_entries[6];
  m_state->m_cellPositionsSet.insert(int(dataEntry.length()));
  for (size_t r=0; r<m_state->m_cellPositions.size(); ++r) {
    auto const &row = m_state->m_cellPositions[r];
    listener->openSheetRow(-16.f, librevenge::RVNG_POINT);
    for (size_t col=0; col<row.size(); ++col) {
      auto p=row[col];
      if (p<0 || p>dataEntry.length()) {
        MWAW_DEBUG_MSG(("MultiplanParser::sendSpreadsheet: find some bad data\n"));
        continue;
      }
      if (!p) continue;
      MWAWVec2i cellPos(static_cast<int>(col), static_cast<int>(r));
      sendCell(cellPos, p);
    }
    listener->closeSheetRow();
  }
  listener->closeSheet();
  return true;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: