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

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

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

#include <librevenge/librevenge.h>


#include "MWAWCell.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWHeader.hxx"
#include "MWAWOLEParser.hxx"
#include "MWAWPrinter.hxx"
#include "MWAWSpreadsheetListener.hxx"
#include "MWAWSubDocument.hxx"

#include "MsWksGraph.hxx"
#include "MsWks3Text.hxx"
#include "MsWksDocument.hxx"

#include "MsWksSSParser.hxx"

/** Internal: the structures of a MsWksSSParser */
namespace MsWksSSParserInternal
{
/** a cell of a MsWksSSParser */
class Cell final : public MWAWCell
{
public:
  /// constructor
  Cell()
    : m_content()
    , m_noteId(0)
  {
  }
  //! destructor
  ~Cell() final;
  //! returns true if the cell do contain any content
  bool isEmpty() const
  {
    return m_content.empty() && !hasBorders() && !m_noteId;
  }

  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Cell const &cell);

  //! the cell content
  MWAWCellContent m_content;

  /** the note id */
  int m_noteId;
};

//! operator<<
std::ostream &operator<<(std::ostream &o, Cell const &cell)
{
  o << static_cast<MWAWCell const &>(cell);

  if (cell.m_noteId) o << ", noteId=" << cell.m_noteId;
  return o;
}

/** the spreadsheet of a of a MsWksSSParser */
class Spreadsheet
{
public:
  //! constructor
  Spreadsheet()
    : m_font(3,12)
    , m_widthCols()
    , m_cells()
    , m_listPageBreaks()
    , m_idNoteMap()
    , m_name("Sheet0")
  {
  }
  //! convert the m_widthCols in a vector of of point size
  std::vector<float> convertInPoint(std::vector<int> const &list, float defSize) const
  {
    auto numCols=size_t(getRightBottomPosition()[0]+1);
    std::vector<float> res;
    res.resize(numCols);
    for (size_t i = 0; i < numCols; i++) {
      if (i>=list.size() || list[i] < 0) res[i] = defSize;
      else res[i] = float(list[i]);
    }
    return res;
  }

  /** the default font */
  MWAWFont m_font;
  /** the column size in pixels(?) */
  std::vector<int> m_widthCols;
  /** the list of not empty cells */
  std::vector<Cell> m_cells;
  /** the list of page break */
  std::vector<int> m_listPageBreaks;
  /** a map id->note content */
  std::map<int,MWAWEntry> m_idNoteMap;
  /** the spreadsheet name */
  std::string m_name;
protected:
  /** returns the last Right Bottom cell position */
  MWAWVec2i getRightBottomPosition() const
  {
    int maxX = 0, maxY = 0;
    for (auto const &cell : m_cells) {
      MWAWVec2i const &p = cell.position();
      if (p[0] > maxX) maxX = p[0];
      if (p[1] > maxY) maxY = p[1];
    }
    return MWAWVec2i(maxX, maxY);
  }
};

Cell::~Cell()
{
}

////////////////////////////////////////
//! Internal: the state of a MsWksSSParser
struct State {
  //! constructor
  State()
    : m_spreadsheet()
    , m_actPage(0)
    , m_numPages(0)
    , m_pageLength(-1)
  {
  }

  /** the spreadsheet */
  Spreadsheet m_spreadsheet;
  int m_actPage /** the actual page */, m_numPages /** the number of page of the final document */;

  //! the page length in point (if known)
  int m_pageLength;
};

////////////////////////////////////////
//! Internal: the subdocument of a MsWksSSParser
class SubDocument final : public MWAWSubDocument
{
public:
  SubDocument(MsWksSSParser &pars, MWAWInputStreamPtr const &input, int zoneId)
    : MWAWSubDocument(&pars, input, MWAWEntry())
    ,m_id(zoneId)
  {
  }

  //! destructor
  ~SubDocument() final {}

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

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

protected:
  /** the subdocument id*/
  int m_id;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType /*type*/)
{
  if (!listener.get()) {
    MWAW_DEBUG_MSG(("MsWksSSParserInternal::SubDocument::parse: no listener\n"));
    return;
  }
  auto *parser = dynamic_cast<MsWksSSParser *>(m_parser);
  if (!parser) {
    MWAW_DEBUG_MSG(("MsWksSSParserInternal::SubDocument::parse: no parser\n"));
    return;
  }
  long pos = m_input->tell();
  parser->sendNote(m_id);
  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_id != sDoc->m_id) return true;
  return false;
}
}


////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
MsWksSSParser::MsWksSSParser(MWAWInputStreamPtr const &input, MWAWRSRCParserPtr const &rsrcParser, MWAWHeader *header)
  : MWAWSpreadsheetParser(input, rsrcParser, header)
  , m_state()
  , m_listZones()
  , m_document()
{
  MWAWInputStreamPtr mainInput=input;
  if (!input) return;
  if (input->isStructured()) {
    MWAWInputStreamPtr mainOle = input->getSubStreamByName("MN0");
    if (mainOle)
      mainInput=mainOle;
  }
  m_document.reset(new MsWksDocument(mainInput, *this));
  init();
}

MsWksSSParser::~MsWksSSParser()
{
}

void MsWksSSParser::init()
{
  resetSpreadsheetListener();
  setAsciiName("main-1");

  m_state.reset(new MsWksSSParserInternal::State);

  // reduce the margin (in case, the page is not defined)
  getPageSpan().setMargins(0.1);
}

////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void MsWksSSParser::parse(librevenge::RVNGSpreadsheetInterface *docInterface)
{
  if (!m_document || !m_document->getInput() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = true;
  try {
#ifdef DEBUG
    MWAWInputStreamPtr &input= getInput();
    if (input->isStructured()) {
      std::shared_ptr<MWAWOLEParser> oleParser(new MWAWOLEParser("MN0", getParserState()->m_fontConverter, 3));
      oleParser->parse(input);
    }
#endif
    // create the asciiFile
    m_document->initAsciiFile(asciiName());

    checkHeader(nullptr);
    createZones();
    ok=!m_state->m_spreadsheet.m_cells.empty();
    if (ok) {
      createDocument(docInterface);
      sendSpreadsheet();
    }
  }
  catch (...) {
    MWAW_DEBUG_MSG(("MsWksSSParser::parse: exception catched when parsing\n"));
    ok = false;
  }
  m_document->ascii().reset();

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

void MsWksSSParser::sendNote(int noteId)
{
  MWAWListenerPtr listener=getSpreadsheetListener();
  if (!listener||
      m_state->m_spreadsheet.m_idNoteMap.find(noteId)==m_state->m_spreadsheet.m_idNoteMap.end()) {
    MWAW_DEBUG_MSG(("MsWksSSParser::sendNote: can not send note %d\n", noteId));
    return;
  }
  MWAWEntry const &entry=m_state->m_spreadsheet.m_idNoteMap.find(noteId)->second;
  int const vers=version();
  if (!entry.valid()) return;
  MWAWInputStreamPtr input = m_document->getInput();
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  while (1) {
    long pos=input->tell();
    if (input->isEnd() || pos>=entry.end()) return;
    auto c = static_cast<int>(input->readULong(1));
    switch (c) {
    case 0x9:
      listener->insertTab();
      break;
    case 0x10: // cursor pos
    case 0x11:
      break;
    default:
      if (c >= 0x15 && c <= 0x19 && vers >= 3) {
        int sz = (c==0x19) ? 0 : (c == 0x18) ? 1 : 2;
        if (sz && pos+1+sz <=  entry.end())
          input->seek(sz, librevenge::RVNG_SEEK_CUR);
        switch (c) {
        case 0x19:
          listener->insertField(MWAWField(MWAWField::Title));
          break;
        case 0x18:
          listener->insertField(MWAWField(MWAWField::PageNumber));
          break;
        case 0x16:
          listener->insertField(MWAWField(MWAWField::Time));
          break;
        case 0x17: // id = 0 : short date ; id=9 : long date
          listener->insertField(MWAWField(MWAWField::Date));
          break;
        case 0x15:
          MWAW_DEBUG_MSG(("MsWksSSParser::sendNote: find unknown field type 0x15\n"));
          break;
        default:
          break;
        }
      }
      else if (c <= 0x1f) {
        MWAW_DEBUG_MSG(("MsWksSSParser::sendNote: find char=%x\n", static_cast<unsigned int>(c)));
      }
      else
        listener->insertCharacter(static_cast<unsigned char>(c), input, entry.end());
      break;
    }

  }
}

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

  std::vector<MWAWPageSpan> pageList;
  m_state->m_actPage = 0;
  m_document->getPageSpanList(pageList, m_state->m_numPages);
  MWAWSpreadsheetListenerPtr listen(new MWAWSpreadsheetListener(*getParserState(), pageList, documentInterface));
  setSpreadsheetListener(listen);
  listen->startDocument();
  // time to send page information the graph parser and the text parser
  m_document->getGraphParser()->setPageLeftTop
  (MWAWVec2f(72.f*float(getPageSpan().getMarginLeft()),
             72.f*float(getPageSpan().getMarginTop())+m_document->getHeaderFooterHeight(true)));
}


////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool MsWksSSParser::createZones()
{
  if (getInput()->isStructured())
    m_document->createOLEZones(getInput());
  MWAWInputStreamPtr input = m_document->getInput();
  const int vers=version();
  long pos;
  if (vers>2) {
    pos = input->tell();
    if (!m_document->readDocumentInfo(0x9a))
      m_document->getInput()->seek(pos, librevenge::RVNG_SEEK_SET);
    pos = input->tell();
    if (m_document->hasHeader() && !m_document->readGroupHeaderFooter(true,99))
      input->seek(pos, librevenge::RVNG_SEEK_SET);
    pos = input->tell();
    if (m_document->hasFooter() && !m_document->readGroupHeaderFooter(false,99))
      input->seek(pos, librevenge::RVNG_SEEK_SET);
  }
  readSSheetZone();

  auto &typeZoneMap=m_document->getTypeZoneMap();
  // fixme: getNewZoneId here
  int mainId=MsWksDocument::Z_MAIN;
  typeZoneMap.insert(std::multimap<int,MsWksDocument::Zone>::value_type
                     (MsWksDocument::Z_MAIN,MsWksDocument::Zone(MsWksDocument::Z_MAIN, mainId)));
  pos = input->tell();
  libmwaw::DebugFile &ascFile = m_document->ascii();
  if (input->isEnd() || input->readLong(2)!=0)
    input->seek(pos, librevenge::RVNG_SEEK_SET);
  else {
    MWAWEntry group;
    group.setId(mainId);
    group.setName("RBDR");
    if (!m_document->m_graphParser->readRB(input,group,1)) {
      MWAW_DEBUG_MSG(("MsWksSSParser::createZones: can not read RBDR group\n"));
      ascFile.addPos(pos+2);
      ascFile.addNote("Entries(RBDR):###");
      input->seek(pos, librevenge::RVNG_SEEK_SET);
    }
  }

  // normally, the file is now parsed, let check for potential remaining zones
  while (!input->isEnd()) {
    MWAW_DEBUG_MSG(("MsWksSSParser::createZones: find some unexpected data\n"));
    pos = input->tell();
    MsWksDocument::Zone unknown;
    if (!m_document->readZone(unknown) || input->tell() <= pos) {
      ascFile.addPos(pos);
      ascFile.addNote("Entries(End)");
      ascFile.addPos(pos+100);
      ascFile.addNote("_");
      break;
    }
  }

  // ok, prepare the data
  m_state->m_numPages = 1;
  std::vector<int> linesH, pagesH;
  m_document->getGraphParser()->computePositions(mainId, linesH, pagesH);
  return true;
}

////////////////////////////////////////////////////////////
// read a spreadsheet zone
////////////////////////////////////////////////////////////
bool MsWksSSParser::readSSheetZone()
{
  auto &sheet=m_state->m_spreadsheet;
  sheet=MsWksSSParserInternal::Spreadsheet();
  int const vers=version();

  MWAWInputStreamPtr input=m_document->getInput();
  libmwaw::DebugFile &ascFile=m_document->ascii();
  long pos = input->tell(), sheetDebPos=pos;
  if (!input->checkPosition(pos+0x30+0x30+0x408)) {
    MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Can not read part A/B\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }

  ascFile.addPos(pos);
  libmwaw::DebugStream f, f2;
  f << "Entries(SSheet): v0=["; // always 8, -1 ?
  for (int i = 0; i < 2; i++)
    f << input->readLong(1) << ",";
  f << "],";

  int N[2]; // max col, max row
  for (auto &n : N) n = static_cast<int>(input->readLong(2));
  f << "maxCol=" << N[0] << ","  << "maxRow=" << N[1] << ",";

  f << "unk0=" << std::hex << input->readULong(4) << std::dec << ","; // big number

  int zoneSize[3];
  zoneSize[0] = static_cast<int>(input->readLong(2));
  if (zoneSize[0]) f << "zone[SheetF]=" << std::hex << zoneSize[0] << std::dec << ",";
  auto numPageBreak = static_cast<int>(input->readLong(2));
  if (numPageBreak) f << "nPgBreak=" << numPageBreak << ",";
  auto numColBreak = static_cast<int>(input->readLong(2));
  if (numColBreak) f << "nColBreak=" << numColBreak << ",";
  zoneSize[1]= static_cast<int>(input->readLong(2));
  if (zoneSize[1]) f << "zone[DocInfo]=" << zoneSize[1] << ",";
  f << "unk1=" << std::hex << input->readULong(4) << std::dec << ","; // big number
  f << std::dec;

  int val;
  for (int i = 0; i < 2; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      f << "w" << i << "=" << val << ",";
  }
  f << "v2=["; // [_,_,_,_,_,_,] or [_,1,3,_,1,3,] or [_,5,5,_,4,2,]
  for (int i = 0; i < 6; i++) {
    val = static_cast<int>(input->readLong(1));
    if (val)
      f << val << ",";
    else
      f << "_,";
  }
  f << "]";
  f << ", v3=["; // [_,_,_,_,] or [_,_,_,1,] or [_,_,_,11,]
  for (int i = 0; i < 4; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      f << val << ",";
    else
      f << "_,";
  }
  f << "]";

  f << ", v4=[" << std::hex;
  for (int i = 0; i < 4; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      f << val << ",";
    else
      f << "_,";
  }
  f << "]" << std::dec;

  ascFile.addNote(f.str().c_str());

  pos = input->tell();
  f.str("");
  ascFile.addPos(pos);
  f << "SSheetB: ";

  std::vector<int> colPos;

  int prevW = -1;
  bool ok = true;
  for (int i = 0; i < 257; i++) {
    auto col = static_cast<int>(input->readULong(2));
    if ((col & 0xFFF) != i+1) {
      ok = false;
      break;
    }
    auto w = static_cast<int>(input->readLong(2));
    if (w > 0) {
      if (prevW > w && i < 200) {
        ok = false;
        break;
      }
      if (prevW > w) w = prevW;
      else prevW = w;
    }
    colPos.push_back(w);
  }

  if (!ok) {
    MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Can not read part B\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }

  f << "lastN=" << input->readULong(2);
  f << ", lastV=" << input->readULong(2);
  f << ", width=[";
  std::vector<int> &colSize = sheet.m_widthCols;
  colSize.resize(256);
  for (size_t i = 0; i < 257; i++) {
    int cPos = colPos[i];
    int lastPos = (i == 0) ? 0 : colPos[i-1];
    if (cPos == 0) f << "_";
    else if (cPos == -1) {
      f << "Inf";
      cPos = 0;
    }
    else f << cPos-lastPos;

    if (i< 256) {
      if (cPos) colSize[i]=cPos-lastPos;
      else colSize[i]=0;
    }

    f << ",";
  }
  f << "]" << std::dec;
  ascFile.addNote(f.str().c_str());

  pos = input->tell();
  f.str("");
  ascFile.addPos(pos);
  long endPos=pos+256;
  f << "SSheetC:";
  int numCharts=0;
  for (int i=0; i < 8; ++i) {
    // the chart names in v2, can we have more than 8 charts ?
    long cPos=input->tell();
    val = static_cast<int>(input->readLong(2));
    auto sSz=static_cast<int>(input->readULong(1));
    if (!sSz || sSz>32-7) {
      input->seek(cPos, librevenge::RVNG_SEEK_SET);
      break;
    }
    ++numCharts;
    f << "Chart" << i << "=[";
    if (val) f << "unkn=" << val << ",";
    std::string name("");
    for (int c=0; c<sSz; ++c)
      name += char(input->readULong(1));
    f << name << ",";
    if ((sSz%2)==0)   input->seek(1, librevenge::RVNG_SEEK_CUR);
    f << "id=" << std::hex << input->readULong(4) << std::dec << "],";
    input->seek(cPos+32, librevenge::RVNG_SEEK_SET);
  }
  int numRemains=int(endPos-input->tell())/2;
  for (int i = 0; i < numRemains; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  ascFile.addNote(f.str().c_str());
  input->seek(endPos, librevenge::RVNG_SEEK_SET);

  pos = input->tell();
  f.str("");
  ascFile.addPos(pos);

  if (!input->checkPosition(pos+52)) {
    MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Can not read part D\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }

  f << "SSheetD:";
  for (int i = 0; i < 5; i++) {
    val = static_cast<int>(input->readLong(2));
    if (i==3) {
      if (val==44) continue;
      f << "unkn=" << val << ",";
    }
    else if (val)
      f << "f" << i << "=" << val << ",";
  }
  f << "dim?=["; // in general 1,1,1,1 but can be X,X,1,1
  for (int i = 0; i < 4; i++)
    f << input->readLong(2) << ",";
  f << "]";

  // always g0=3ffe, g1=100 ?
  for (int i = 0; i < 2; i++) {
    f << ", g" << i << "="  << std::hex << input->readLong(2) << std::dec;
  }
  auto fId = static_cast<int>(input->readLong(2));
  auto fSize = static_cast<int>(input->readLong(2));
  sheet.m_font = MWAWFont(fId, float(fSize));
  f << ", [" <<  sheet.m_font.getDebugString(getParserState()->m_fontConverter) << "]";
  // , [0,{33|255},] ?
  f << ", unk=[" << std::dec;
  for (int i = 0; i < 2; i++)
    f << input->readLong(2) << ",";
  f << "]";
  int numRemain=int(sheetDebPos+m_document->getLengthOfFileHeader3()-input->tell())/2;
  for (int i = 0; i < numRemain; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  ascFile.addNote(f.str().c_str());
  input->seek(sheetDebPos+m_document->getLengthOfFileHeader3(), librevenge::RVNG_SEEK_SET);

  // part E
  MWAWVec2i cellPos(0,0);
  while (1) {
    if (cellPos[1] == N[1]) break;

    long posRow = input->tell();
    unsigned long length = input->readULong(4);
    if (length & 0xF0000000L) {
      int nRow = length & 0x0FFFFFFFL;
      if ((length >> 28) != 8 || std::numeric_limits<int>::max() - cellPos[1] < nRow) {
        input->seek(posRow, librevenge::RVNG_SEEK_SET);
        break;
      }
      f.str("");
      f << "SSheetE:skipRow=" << std::hex << nRow;
      cellPos[1]+=nRow;
      ascFile.addPos(posRow);
      ascFile.addNote(f.str().c_str());
      continue;
    }
    long endRow = posRow+long(length)+4;

    if (!input->checkPosition(endRow)) {
      MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Can not read part E(row)\n"));
      input->seek(posRow, librevenge::RVNG_SEEK_SET);
      return false;
    }

    f.str("");
    cellPos[0]=0;
    f << "SSheetE(Row" << std::dec << cellPos[1]+1 << "): ";
    while (input->tell() < endRow-1) {
      pos = input->tell();
      auto sz = static_cast<int>(input->readULong(1));

      if (sz == 0xFE) { // skip next cell
        auto numC = static_cast<int>(input->readULong(1));
        f2.str("");
        f2 << "SSheetE:skipC=" << numC;
        cellPos[0]+=numC;
        ascFile.addPos(pos);
        ascFile.addNote(f2.str().c_str());
        continue;
      }

      if (sz == 0) {
        ascFile.addPos(pos);
        ascFile.addNote("SSheetE:empty");
        ++cellPos[0];
        continue;
      }
      MsWksSSParserInternal::Cell cell;
      if (!readCell(sz, cellPos, cell)) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        break;
      }
      if (!cell.isEmpty())
        sheet.m_cells.push_back(cell);
      ++cellPos[0];
    }

    ++cellPos[1];
    if (input->tell() != endRow-1) {
      MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Can not read part E(cells)\n"));
      input->seek(posRow, librevenge::RVNG_SEEK_SET);
      return false;
    }
    ascFile.addDelimiter(endRow-1,'|');
    val = static_cast<int>(input->readULong(1));
    if (val == 0) f << "*";
    else if (val != 0xFF)
      f << "##end=" << std::hex << val << std::dec;

    ascFile.addPos(posRow);
    ascFile.addNote(f.str().c_str());
    if (val == 0)
      break;
  }

  pos = input->tell();
  if (!input->checkPosition(pos+zoneSize[0])) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Can not read part F\n"));
    return false;
  }

  f.str("");
  f << "SSheetF[A]:";
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  input->seek(pos+zoneSize[0]-10, librevenge::RVNG_SEEK_SET);

  pos = input->tell();
  f.str("");
  f << "SSheetF[B]:" << std::dec;
  for (int i = 0; i < 4; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  val = static_cast<int>(input->readULong(2));
  if (val) f << std::hex << "fl=" << val << "," << std::dec;
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  if (numPageBreak || numColBreak) {
    pos=input->tell();
    f.str("");
    f << "SSheetG:";
    if (numPageBreak > 0) {
      sheet.m_listPageBreaks.resize(size_t(numPageBreak));
      f << "pgBreak=[";
      for (auto &pgBreak : sheet.m_listPageBreaks) {
        pgBreak = static_cast<int>(input->readULong(2));
        f << pgBreak << ",";
      }
      f << "],";
    }
    // columns/notes break?
    if (numColBreak) {
      f << "colBreak=[";
      for (int i = 0; i < numColBreak; i++)
        f << input->readULong(2) << ",";
      f << "],";
    }
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }

  if (zoneSize[1]>0) {
    // only version<=2 seems to contains the print data with size 0x15e
    pos = input->tell();
    MWAWEntry zone;
    if (!m_document->readDocumentInfo(zoneSize[1])) {
      input->seek(pos+zoneSize[1], librevenge::RVNG_SEEK_SET);
      MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:can not read print info\n"));
      ascii().addPos(pos);
      ascii().addNote("SSheet[printInfo]:###");
    }
  }

  if (vers==2) {
    for (int i=0; i < numCharts; ++i) {
      pos=input->tell();
      if (!input->checkPosition(pos+256)) {
        MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:can not find chart %d data\n", i));
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        return false;
      }
      f.str("");
      f << "Entries(Chart)[" << i << "]:";
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      input->seek(pos+256, librevenge::RVNG_SEEK_SET);
    }
  }

  long HDebPos = input->tell();
  f.str("");
  f << "SSheetH:" << std::dec;
  int numNote = 0;
  while (1) {
    pos = input->tell();
    auto idNote = static_cast<int>(input->readLong(2));
    if (idNote == 0) break;
    if (idNote != ++numNote) {
      input->seek(pos, librevenge::RVNG_SEEK_CUR);
      MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Pb when reading a note\n"));
      return false;
    }
    // a pascal string
    auto pSz = static_cast<int>(input->readLong(2));
    MWAWEntry note;
    note.setBegin(input->tell());
    note.setLength(pSz);
    std::string str="";
    for (int s = 0; s < pSz; s++)
      str += char(input->readLong(1));
    if (input->tell() != pos+4+pSz) {
      input->seek(pos, librevenge::RVNG_SEEK_CUR);
      MWAW_DEBUG_MSG(("MsWksSSParser::readSSheetZone:Pb when reading a note\n"));
      return false;
    }
    if (pSz) {
      sheet.m_idNoteMap[idNote] = note;
      f << "Note" << idNote << "=\"" << str << "\",";
    }
  }

  ascFile.addPos(HDebPos);
  ascFile.addNote(f.str().c_str());
  return true;
}

bool MsWksSSParser::readCell(int sz, MWAWVec2i const &cellPos, MsWksSSParserInternal::Cell &cell)
{
  int const vers=version();
  cell = MsWksSSParserInternal::Cell();
  cell.setPosition(cellPos);
  if (sz == 0xFF || sz < 5) return false;

  MWAWInputStreamPtr input=m_document->getInput();
  long debPos = input->tell();

  libmwaw::DebugFile &ascFile=m_document->ascii();
  libmwaw::DebugStream f;
  long endPos = debPos+sz-5;
  input->seek(endPos+5, librevenge::RVNG_SEEK_SET);
  if (input->tell() !=endPos+5) return false;

  // read the flag
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  int fl[5];
  for (auto &flag : fl) flag = static_cast<int>(input->readULong(1));
  MWAWCell::Format format;
  MWAWCellContent &content=cell.m_content;
  int style = 0, align = 0, subformat = 0;
  if (vers >= 3) {
    style = (fl[4]>>1) & 0x1f;
    align = (fl[4]>>6);
    fl[4] &= 1;
    subformat = fl[2] >> 4;
    cell.setBorders(fl[2] & 0xF, MWAWBorder()); // checkme
    fl[2] = 0;
  }
  else {
    style = fl[4] & 0x18;
    align = (fl[4]>>5)&3;
    subformat= (fl[4]&7);
    fl[4] &= 0x80;
  }
  switch (align) {
  case 0:
    cell.setHAlignment(MWAWCell::HALIGN_LEFT);
    break;
  case 1:
    cell.setHAlignment(MWAWCell::HALIGN_CENTER);
    break;
  case 2:
    cell.setHAlignment(MWAWCell::HALIGN_RIGHT);
    break;
  default:
  case 3:
    break; // default
  }
  cell.m_noteId = fl[1] & 0xf;
  fl[1] >>= 4;
  int type = fl[3] >> 4;
  format.m_digits=(fl[3] & 0xf);
  fl[3] &= 1;

  MWAWFont font=m_state->m_spreadsheet.m_font;
  MWAWColor col;
  if (vers>=3&&m_document->getColor(fl[0],col,3)) {
    font.setColor(col);
    fl[0]=0;
  }
  uint32_t fflags = 0;
  if (style) {
    if (style & 1) fflags |= MWAWFont::shadowBit;
    if (style & 2) fflags |= MWAWFont::embossBit;
    if (style & 4) fflags |= MWAWFont::italicBit;
    if (style & 8) font.setUnderlineStyle(MWAWFont::Line::Simple);
    if (style & 16) fflags |= MWAWFont::boldBit;
  }
  font.setFlags(fflags);
  cell.setFont(font);

  content.m_contentType=MWAWCellContent::C_NONE;
  if (type & 2) { // checkme: is this also ok for v2?
    cell.setProtected(true);
    type &= 0xFD;
  }
  switch (type>>2) {
  case 0:
    content.m_contentType=MWAWCellContent::C_TEXT;
    format.m_format=MWAWCell::F_TEXT;
    break;
  case 1:
    format.m_format=MWAWCell::F_NUMBER;
    content.m_contentType=MWAWCellContent::C_NUMBER;
    break;
  case 3:
    f << "type" << type << ",";
    MWAW_FALLTHROUGH;
  case 2: //number general
    format.m_format=MWAWCell::F_NUMBER;
    content.m_contentType=MWAWCellContent::C_FORMULA;
    break;
  default:
    break;
  }
  if (vers <= 2 && format.m_format==MWAWCell::F_NUMBER) {
    switch (subformat) {
    case 0:
      format.m_numberFormat=MWAWCell::F_NUMBER_DECIMAL;
      break;
    case 1:
      format.m_numberFormat=MWAWCell::F_NUMBER_CURRENCY;
      break;
    case 2:
      format.m_numberFormat=MWAWCell::F_NUMBER_PERCENT;
      break;
    case 4:
      format.m_numberFormat=MWAWCell::F_NUMBER_SCIENTIFIC;
      break;
    case 5:
    case 6:
      format.m_format=MWAWCell::F_DATE;
      break;
    case 7:
      format.m_format=MWAWCell::F_TIME;
      break;
    case 3: // generic
    default:
      format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
      break;
    }
  }
  else if (vers>2) {
    if (type&1) {
      format.m_format= (subformat < 4) ? MWAWCell::F_TIME : MWAWCell::F_DATE;
      fl[3] = 0;
    }
    switch (format.m_format) {
    case  MWAWCell::F_NUMBER:
      format.m_numberFormat=MWAWCell::F_NUMBER_GENERIC;
      switch (subformat) {
      case 0:
        break;
      case 1:
        format.m_numberFormat=MWAWCell::F_NUMBER_DECIMAL;
        break;
      case 2:
        format.m_numberFormat=MWAWCell::F_NUMBER_CURRENCY;
        break;
      case 3:
        format.m_thousandHasSeparator=true;
        break;
      case 4:
        format.m_numberFormat=MWAWCell::F_NUMBER_CURRENCY;
        format.m_thousandHasSeparator=true;
        break;
      case 5:
        format.m_numberFormat=MWAWCell::F_NUMBER_SCIENTIFIC;
        break;
      case 6:
        format.m_numberFormat=MWAWCell::F_NUMBER_PERCENT;
        break;
      case 7:
        format.m_numberFormat=MWAWCell::F_NUMBER_PERCENT;
        format.m_thousandHasSeparator=true;
        break;
      default:
        f << ",subform==##unkn" << subformat;
        break;
      }
      break;
    case MWAWCell::F_TIME:
      if (subformat >= 0 && subformat < 4) {
        static char const *wh[]= {"%I:%M:%S %p", "%I:%M %p", "%H:%M:%S", "%H:%M"};
        format.m_DTFormat=wh[subformat];
      }
      else
        f << ",subform==##unkn" << subformat;
      break;
    case MWAWCell::F_DATE:
      switch (subformat) {
      case 4:
      case 5:
      case 6:
      case 7:
      case 8: {
        static char const *wh[]= {"%m/%d/%y", "%b %d, %Y", "%b, %d", "%b, %Y", "%a, %d %b, %Y" };
        format.m_DTFormat=wh[subformat-4];
        break;
      }
      case 10:
      case 11:
        format.m_DTFormat="%B %d %Y";
        break;
      case 12:
      case 13:
        format.m_DTFormat="%A, %B %d, %Y";
        break;
      default:
        f << ",subform==##unkn" << subformat;
        break;
      }
      break;
    case MWAWCell::F_TEXT:
      break;
    case MWAWCell::F_BOOLEAN:
    case MWAWCell::F_UNKNOWN:
      MWAW_DEBUG_MSG(("MsWksSSParser::readCell: unexpected format\n"));
      break;
#if !defined(__clang__)
    default:
      f << ",subform==##unkn" << subformat;
      break;
#endif
    }
  }
  long pos = debPos;
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  if (pos != endPos) {
    bool ok = true;
    switch (content.m_contentType) {
    case MWAWCellContent::C_TEXT: {
      content.m_textEntry.setBegin(pos);
      content.m_textEntry.setEnd(endPos);
      if (content.m_textEntry.length()>=0) {
        std::string text("");
        for (long i=0; i <content.m_textEntry.length(); ++i)
          text += char(input->readULong(1));
        f << text << ",";
      }
      else
        f << "###text,";
      break;
    }
    case MWAWCellContent::C_NUMBER: {
      double val;
      bool isNan;
      std::string str;
      if (!m_document->readDBNumber(endPos, val, isNan, str)) {
        ok=false;
        f << "###number";
      }
      else
        content.setValue(val);
      f << val;
      if (!str.empty()) f << "[" << str << "],";
      break;
    }
    case MWAWCellContent::C_FORMULA: {
      std::string extra("");
      if (!m_document->readFormula(endPos, content, extra)) {
        ok=false;
        f << "###";
      }
      f << extra;
      break;
    }
    case MWAWCellContent::C_NONE:
      break;
    case MWAWCellContent::C_UNKNOWN:
#if !defined(__clang__)
    default:
#endif
      ok = false;
    }
    if (!ok)
      f << ",###pb";
  }
  cell.setFormat(format);
  // change the reference date from 1/1/1904 to 1/1/1900
  if (format.m_format==MWAWCell::F_DATE && content.isValueSet())
    content.setValue(content.m_value+1460.);

  std::string extra = f.str();
  f.str("");
  f << "SSheetE:" << cell << ",unk=[" << std::hex;
  for (auto flag : fl) {
    if (!flag) f << "_,";
    else f << flag << ",";
  }
  f << "]" << std::dec;
  if (extra.size()) f << ", extra=" << extra;


  pos = input->tell();
  if (pos != endPos) {
    ascFile.addDelimiter(pos,'[');
    ascFile.addDelimiter(endPos,']');
  }

  ascFile.addPos(debPos-1);
  ascFile.addNote(f.str().c_str());
  input->seek(endPos+5, librevenge::RVNG_SEEK_SET);
  return true;
}

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
bool MsWksSSParser::sendSpreadsheet()
{
  MWAWSpreadsheetListenerPtr listener=getSpreadsheetListener();
  MWAWInputStreamPtr &input= m_document->getInput();
  if (!listener) {
    MWAW_DEBUG_MSG(("MsWksSSParser::sendSpreadsheet: I can not find the listener\n"));
    return false;
  }
  auto &sheet = m_state->m_spreadsheet;

  int prevRow = -1;
  listener->openSheet(sheet.convertInPoint(sheet.m_widthCols,76), librevenge::RVNG_POINT, std::vector<int>(), sheet.m_name);
  // fixme: this sends the pictures present in the main page, but not the picture present in header/footer
  auto zone=m_document->getZone(MsWksDocument::Z_MAIN);
  m_document->getGraphParser()->sendAll(zone.m_zoneId, true);

  for (auto const &cell : sheet.m_cells) {
    // FIXME: find the row height...
    if (cell.position()[1]>prevRow+1) {
      if (prevRow != -1) listener->closeSheetRow();
      int numRepeat=cell.position()[1]-1-prevRow;
      listener->openSheetRow(16, librevenge::RVNG_POINT, numRepeat);
      prevRow+=numRepeat;
    }
    if (cell.position()[1] > prevRow) {
      if (prevRow != -1) listener->closeSheetRow();
      ++prevRow;
      listener->openSheetRow(16, librevenge::RVNG_POINT);
    }
    listener->openSheetCell(cell, cell.m_content);
    if (cell.m_content.m_textEntry.valid()) {
      listener->setFont(cell.isFontSet() ? cell.getFont() : sheet.m_font);
      input->seek(cell.m_content.m_textEntry.begin(), librevenge::RVNG_SEEK_SET);
      while (!input->isEnd() && input->tell()<cell.m_content.m_textEntry.end()) {
        auto c=static_cast<unsigned char>(input->readULong(1));
        if (c==0xd)
          listener->insertEOL();
        else
          listener->insertCharacter(c);
      }
    }
    if (cell.m_noteId>0) {
      MWAWSubDocumentPtr subDoc(new MsWksSSParserInternal::SubDocument(*this, input, cell.m_noteId));
      listener->insertComment(subDoc);
    }
    listener->closeSheetCell();
  }
  if (prevRow!=-1) listener->closeSheetRow();
  listener->closeSheet();
  return true;
}

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

////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool MsWksSSParser::checkHeader(MWAWHeader *header, bool strict)
{
  *m_state = MsWksSSParserInternal::State();
  if (!m_document || !m_document->checkHeader3(header, strict)) return false;
  if (m_document->getKind() != MWAWDocument::MWAW_K_SPREADSHEET)
    return false;
#ifndef DEBUG
  if (version() == 1)
    return false;
#endif
  return true;
}

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