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 "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWListener.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSubDocument.hxx"

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

#include "RagTime5Text.hxx"

#include "libmwaw_internal.hxx"

/** Internal: the structures of a RagTime5Text */
namespace RagTime5TextInternal
{
//! a PLC of a RagTime5Text
struct PLC {
  //! constructor
  PLC()
    : m_position(-1)
    , m_fileType(0)
    , m_value(-1)
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, PLC const &plc)
  {
    if (plc.m_fileType==0) {
      o << "free[next]";
      if (plc.m_position>0)
        o << "=PLC" << plc.m_position;
      o << ",";
      return o;
    }
    if (plc.m_position>=0) o << "pos=" << plc.m_position << ",";
    switch (plc.m_fileType) {
    case 0:
      break;
    case 0x1001:
      o << "para,";
      break;
    case 0x5001:
      o << "char,";
      break;
    default:
      if (plc.m_fileType&0xfe) o << "#";
      o << "type=" << std::hex << plc.m_fileType << std::dec << ",";
    }
    if (plc.m_value!=-1) o << "f0=" << plc.m_value << ",";
    return o;
  }
  //! the position in the text
  int m_position;
  //! the file type
  int m_fileType;
  //! an unknown value
  int m_value;
};

//! a small struct use to define a block of a RagTime5Text
struct Block {
  //! constructor
  Block()
    : m_id(0)
    , m_subId(0)
    , m_dimension()
    , m_extra("")
  {
    for (auto &plc : m_plc) plc=0;
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Block const &block)
  {
    o << "id=" << block.m_id << ",";
    if (block.m_subId) o << "id[sub]=" << block.m_subId << ",";
    o << "PLC" << block.m_plc[0] << "<->" << block.m_plc[1] << ",";
    o << block.m_dimension << ",";
    o << block.m_extra;
    return o;
  }
  //! the block id
  int m_id;
  //! the block sub id
  int m_subId;
  //! the block dimension
  MWAWBox2f m_dimension;
  //! the list of zone plc (first-end)
  int m_plc[2];
  //! extra data
  std::string m_extra;
};

//! low level: the text cluster of a RagTime5Text
struct ClusterText final : public RagTime5ClusterManager::Cluster {
  //! constructor
  ClusterText()
    : RagTime5ClusterManager::Cluster(C_TextZone)
    , m_contentLink()
    , m_plcDefLink()
    , m_plcDefFreeBegin(0)
    , m_plcDefNumFree(-1)
    , m_plcToStyleLink()
    , m_block2ToPlcLink()
    , m_separatorLink()
    , m_linkDefList()
    , m_blockList()
    , m_block2List()
    , m_PLCList()
    , m_posToStyleIdMap()
  {
  }
  //! destructor
  ~ClusterText() final;
  //! the main content
  RagTime5ClusterManager::Link m_contentLink;
  //! the plc definition link
  RagTime5ClusterManager::Link m_plcDefLink;
  //! the plc first free block in the plc definition list
  int m_plcDefFreeBegin;
  //! the number of free block in the plc definition list
  int m_plcDefNumFree;
  //! the plc to text style link
  RagTime5ClusterManager::Link m_plcToStyleLink;
  //! the block2 to plc link
  RagTime5ClusterManager::Link m_block2ToPlcLink;
  //! the word/separator link
  RagTime5ClusterManager::Link m_separatorLink;
  //! cluster links 0: list of size 10(pipeline?) and 14(graphic?), 1: list of size 12(related to link)
  RagTime5ClusterManager::Link m_clusterLink[2];
  //! the list of link zone
  std::vector<RagTime5ClusterManager::Link> m_linkDefList;
  //! list of unknown link: first with field of size 14, 2th with field of size 12
  RagTime5ClusterManager::Link m_unknownLink[2];

  // final data

  //! list of block (defined in header)
  std::vector<std::vector<Block> > m_blockList;
  //! list of block (defined in block2 list)
  std::vector<Block> m_block2List;
  //! the PLC list
  std::vector<PLC> m_PLCList;
  //! position to plc map
  std::multimap<int, int> m_posToStyleIdMap;
};

ClusterText::~ClusterText()
{
}

////////////////////////////////////////
//! Internal: the state of a RagTime5Text
struct State {
  //! constructor
  State()
    : m_numPages(0)
    , m_idTextMap()
  {
  }
  //! the number of pages
  int m_numPages;
  //! map data id to text zone
  std::map<int, std::shared_ptr<ClusterText> > m_idTextMap;
};

}

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

RagTime5Text::~RagTime5Text()
{ }

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

int RagTime5Text::numPages() const
{
  // TODO IMPLEMENT ME
  MWAW_DEBUG_MSG(("RagTime5Text::numPages: is not implemented\n"));
  return 0;
}

bool RagTime5Text::send(int zoneId, MWAWListenerPtr listener, int partId, int block2Id)
{
  auto it=m_state->m_idTextMap.find(zoneId);
  if (it==m_state->m_idTextMap.end() || !it->second) {
    MWAW_DEBUG_MSG(("RagTime5Text::send: can not find zone %d\n", zoneId));
    return false;
  }
  return send(*it->second, listener, partId, block2Id);
}

////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// text separator position
////////////////////////////////////////////////////////////
bool RagTime5Text::readTextSeparators(RagTime5Zone &zone, std::vector<int> &separators)
{
  if (!zone.m_entry.valid() || zone.getKindLastPart(zone.m_kinds[1].empty())!="ItemData") {
    MWAW_DEBUG_MSG(("RagTime5Text::readTextSeparators: can not find the text position zone\n"));
    return false;
  }

  zone.m_isParsed=true;
  MWAWEntry entry=zone.m_entry;
  MWAWInputStreamPtr input=zone.getInput();
  libmwaw::DebugFile &ascFile=zone.ascii();
  libmwaw::DebugStream f;
  f << "Entries(TextSep)[" << zone << "]:";

  input->seek(zone.m_entry.begin(), librevenge::RVNG_SEEK_SET);
  separators.resize(size_t(2*entry.length()));

  int lastSeen=0, numSeen=0;
  for (long i=0; i<entry.length(); ++i) {
    auto c=static_cast<int>(input->readULong(1));
    for (int j=0; j<2; ++j) {
      int v=(j==0 ? (c>>4) : c)&0xf;
      if (v!=lastSeen) {
        if (numSeen==1) f << lastSeen << ",";
        else if (numSeen) f << lastSeen << "x" << numSeen << ",";
        numSeen=0;
        lastSeen=v;
      }
      ++numSeen;
      separators[size_t(2*i+j)]=v;
    }
  }
  if (numSeen==1) f << lastSeen << ",";
  else if (numSeen) f << lastSeen << "x" << numSeen << ",";

  ascFile.addPos(entry.end());
  ascFile.addNote("_");
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// link/list definition
////////////////////////////////////////////////////////////
bool RagTime5Text::readFieldZones(RagTime5ClusterManager::Cluster &/*cluster*/, RagTime5ClusterManager::Link const &link,
                                  bool isDefinition)
{
  if (link.m_ids.size()<2 || !link.m_ids[1])
    return false;

  std::vector<long> decal;
  if (link.m_ids[0])
    m_mainParser.readPositions(link.m_ids[0], decal);
  if (decal.empty())
    decal=link.m_longList;

  int const dataId=link.m_ids[1];
  auto dataZone=m_mainParser.getDataZone(dataId);
  auto N=int(decal.size());

  if (!dataZone || !dataZone->m_entry.valid() ||
      dataZone->getKindLastPart(dataZone->m_kinds[1].empty())!="ItemData" || N<=1) {
    if (N==1 && dataZone && !dataZone->m_entry.valid()) {
      // a zone with 0 zone is ok...
      dataZone->m_isParsed=true;
      return true;
    }
    MWAW_DEBUG_MSG(("RagTime5Text::readFieldZones: the data zone %d seems bad\n", dataId));
    return false;
  }

  dataZone->m_isParsed=true;
  MWAWEntry entry=dataZone->m_entry;
  libmwaw::DebugFile &ascFile=dataZone->ascii();
  libmwaw::DebugStream f;
  std::string name(isDefinition ? "FieldDef" : "FieldPos");
  f << "Entries(" << name << ")[" << *dataZone << "]:";
  ascFile.addPos(entry.end());
  ascFile.addNote("_");
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());

  MWAWInputStreamPtr input=dataZone->getInput();
  input->setReadInverted(!dataZone->m_hiLoEndian);
  long debPos=entry.begin();
  long endPos=entry.end();

  for (int i=0; i<N-1; ++i) {
    long pos=decal[size_t(i)];
    long nextPos=decal[size_t(i+1)];
    if (nextPos==pos) continue;
    if (pos<0 || debPos+nextPos>endPos || pos>nextPos) {
      MWAW_DEBUG_MSG(("RagTime5Text::readFieldZones: can not read the data zone %d-%d seems bad\n", dataId, i));
      if (debPos+pos<endPos) {
        f.str("");
        f << name << "-" << i+1 << ":###";
        ascFile.addPos(debPos+pos);
        ascFile.addNote(f.str().c_str());
      }
      continue;
    }
    input->seek(debPos+pos, librevenge::RVNG_SEEK_SET);
    if ((isDefinition && readFieldDefinition(*dataZone, debPos+nextPos,i+1)) ||
        (!isDefinition && readFieldPosition(*dataZone, debPos+nextPos,i+1)))
      continue;
    f.str("");
    f << name << "-" << i+1 << ":";
    ascFile.addPos(debPos+pos);
    ascFile.addNote(f.str().c_str());
  }

  input->setReadInverted(false);
  return true;
}

bool RagTime5Text::readFieldDefinition(RagTime5Zone &zone, long endPos, int n)
{
  MWAWInputStreamPtr input=zone.getInput();
  long pos=input->tell();
  libmwaw::DebugFile &ascFile=zone.ascii();
  libmwaw::DebugStream f;
  f << "FieldDef-" << n << ":";
  if (pos+6>endPos) {
    MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: the zone seems too short\n"));
    f<<"###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  int decal[5]= {6,0,0,0,int(endPos-pos)};
  for (int i=1; i<4; ++i) {
    decal[i]=static_cast<int>(input->readULong(2));
    if (decal[i]==0) continue;
    if (decal[i]&0x8000) {
      f << "fl" << i << ",";
      decal[i] &= 0x7FFF;
    }
    if (decal[i]<6 || pos+decal[i]>=endPos) {
      MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: the %d pointer seems bad\n", i));
      f << "##decal[" << i << "]=" << decal[i] << ",";
      decal[i]=0;
      continue;
    }
  }
  for (int i=3; i>=1; --i) {
    if (!decal[i])
      decal[i]=decal[i+1];
  }
  for (int i=0; i<4; ++i) {
    if (decal[i+1]==decal[i])
      continue;
    if (decal[i+1]<decal[i]) {
      MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: the %d pointer seems bad\n", i));
      f << "##decal" << i << ",";
      continue;
    }
    switch (i) {
    case 0: {
      if (decal[i+1]-decal[i]<8) {
        MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: the zone 0 size seems bad\n"));
        f << "##decal2,";
        break;
      }
      input->seek(pos+decal[i], librevenge::RVNG_SEEK_SET);
      auto val=static_cast<int>(input->readLong(2)); // always 0?
      if (val) f<< "#f0=" << val << ",";
      val=static_cast<int>(input->readLong(2)); // small value often equal to 100
      if (val) f << "f1=" << val << ",";
      f << "f2=" << input->readULong(2) << ","; // big number
      if (input->tell()!=pos+decal[i+1]) {
        static bool first=true;
        if (first) {
          first=false;
          MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: sorry, reading formula is not implemented\n"));
        }
        f << "hasForm,";
        ascFile.addDelimiter(input->tell(),'|');
      }
      ascFile.addDelimiter(pos+decal[i+1],'|');
      break;
    }
    case 1:
      ascFile.addDelimiter(pos+decal[i+1],'|');
      break;
    case 2: {
      // list of small int
      if ((decal[i+1]-decal[i])%4) {
        MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: the zone 2 size seems bad\n"));
        f << "##decal2,";
        break;
      }
      input->seek(pos+decal[i], librevenge::RVNG_SEEK_SET);
      long endDataPos=pos+decal[i+1];
      f << "list2=[";
      while (!input->isEnd()) {
        long begDataPos=input->tell();
        if (begDataPos==endDataPos) break;
        if (begDataPos+4>endDataPos) {
          MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: problem with length for zone 2\n"));
          f << "#end,";
          break;
        }
        // FIXME: this list is either a simple list or a list of data
        auto lVal=long(input->readULong(4));
        if ((lVal>>24)==3) {
          f << std::hex << (lVal&0xFFFFFF) << std::dec << ",";
          continue;
        }
        input->seek(begDataPos, librevenge::RVNG_SEEK_SET);
        std::vector<int> listIds;
        if (begDataPos+8>endDataPos || !m_structManager->readDataIdList(input, 1, listIds)) {
          MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: can not read data  for zone 2\n"));
          f << "#type=" << std::hex << lVal << std::dec << ",";
          break;
        }
        if (listIds[0]) // some cluster data
          f << "data" << listIds[0] << "A:";
        lVal=long(input->readULong(4));
        f << std::hex << (lVal&0xFFFFFF) << std::dec;
        if ((lVal>>24)!=3) f << "[" << (lVal>>24) << "]";
        f << ",";
      }
      f << "],";
      break;
    }
    case 3: { // list of small int
      if ((decal[i+1]-decal[i])%2) {
        MWAW_DEBUG_MSG(("RagTime5Text::readFieldDefinition: the zone 3 size seems bad\n"));
        f << "##decal3,";
        break;
      }
      int N=(decal[i+1]-decal[i])/2;
      input->seek(pos+decal[i], librevenge::RVNG_SEEK_SET);
      f << "list3=[";
      for (int j=0; j<N; ++j)
        f << input->readLong(2) << ",";
      f << "],";
      break;
    }
    default:
      break;
    }
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return true;
}

bool RagTime5Text::readFieldPosition(RagTime5Zone &zone, long endPos, int n)
{
  MWAWInputStreamPtr input=zone.getInput();
  long pos=input->tell();
  libmwaw::DebugFile &ascFile=zone.ascii();
  libmwaw::DebugStream f;
  f << "FieldPos-" << n << ":";
  if ((endPos-pos)%8) {
    MWAW_DEBUG_MSG(("RagTime5Text::readFieldPosition: the zone seems bad\n"));
    f<<"###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  auto N=int((endPos-pos)/8);
  f << "cluster=[";
  for (int i=0; i<N; ++i) {
    long actPos=input->tell();
    std::vector<int> listIds; // the cluster which contains the definition
    if (!m_structManager->readDataIdList(input, 1, listIds)) {
      MWAW_DEBUG_MSG(("RagTime5Text::readFieldPosition: find unknown block type\n"));
      f << "##type,";
      input->seek(actPos+8, librevenge::RVNG_SEEK_SET);
      continue;
    }
    auto id=static_cast<int>(input->readULong(4));
    if (listIds[0]==0) f << "_,";
    else f << "data" << listIds[0] << "A-TF" << id << ",";
  }
  f << "],";
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// link/list definition
////////////////////////////////////////////////////////////
bool RagTime5Text::readLinkZones(RagTime5ClusterManager::Cluster &cluster, RagTime5ClusterManager::Link const &link)
{
  if (link.m_ids.empty()) {
    MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: can not find the first zone id\n"));
    return false;
  }
  if (link.m_ids.size()>=3 && link.m_ids[2]) {
    std::vector<long> decal;
    if (link.m_ids[1])
      m_mainParser.readPositions(link.m_ids[1], decal);
    if (decal.empty())
      decal=link.m_longList;
    int const dataId=link.m_ids[2];
    auto dataZone=m_mainParser.getDataZone(dataId);
    if (!dataZone || !dataZone->m_entry.valid() ||
        dataZone->getKindLastPart(dataZone->m_kinds[1].empty())!="ItemData") {
      if (decal.size()==1) {
        // a graphic zone with 0 zone is ok...
        dataZone->m_isParsed=true;
      }
      MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: the data zone %d seems bad\n", dataId));
    }
    else {
      MWAWEntry entry=dataZone->m_entry;
      dataZone->m_isParsed=true;

      libmwaw::DebugFile &ascFile=dataZone->ascii();
      libmwaw::DebugStream f;
      f << "Entries(LinkDef)[" << *dataZone << "]:";
      ascFile.addPos(entry.end());
      ascFile.addNote("_");

      if (decal.size() <= 1) {
        MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: can not find position for the data zone %d\n", dataId));
        f << "###";
        ascFile.addPos(entry.begin());
        ascFile.addNote(f.str().c_str());
      }
      else {
        auto N=int(decal.size());
        MWAWInputStreamPtr input=dataZone->getInput();
        input->setReadInverted(!cluster.m_hiLoEndian); // checkme maybe zone

        ascFile.addPos(entry.begin());
        ascFile.addNote(f.str().c_str());

        for (int i=0; i<N-1; ++i) {
          long pos=decal[size_t(i)], nextPos=decal[size_t(i+1)];
          if (pos==nextPos) continue;
          if (pos<0 || pos>entry.length()) {
            MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: can not read the data zone %d-%d seems bad\n", dataId, i));
            continue;
          }
          f.str("");
          f << "LinkDef-" << i+1 << ":";
          librevenge::RVNGString string;
          input->seek(pos+entry.begin(), librevenge::RVNG_SEEK_SET);
          if (nextPos>entry.length() || !m_structManager->readUnicodeString(input, entry.begin()+nextPos, string)) {
            MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: can not read a string\n"));
            f << "###";
          }
          else if (!string.empty() && string.cstr()[0]=='\0')
            f << "\"" << string.cstr()+1 << "\",";
          else
            f << "\"" << string.cstr() << "\",";
          ascFile.addPos(entry.begin()+pos);
          ascFile.addNote(f.str().c_str());
        }
        input->setReadInverted(false);
      }
    }
  }
  // ok no list
  if (!link.m_ids[0])
    return true;
  auto dataZone=m_mainParser.getDataZone(link.m_ids[0]);
  if (!dataZone || dataZone->getKindLastPart()!="ItemData") {
    MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: can not find the first zone %d\n", link.m_ids[0]));
    return false;
  }

  // ok no list
  if (!dataZone->m_entry.valid())
    return true;

  MWAWInputStreamPtr input=dataZone->getInput();
  bool const hiLo=dataZone->m_hiLoEndian;
  input->setReadInverted(!hiLo);
  input->seek(dataZone->m_entry.begin(), librevenge::RVNG_SEEK_SET);
  dataZone->m_isParsed=true;

  libmwaw::DebugFile &ascFile=dataZone->ascii();
  libmwaw::DebugStream f;
  ascFile.addPos(dataZone->m_entry.end());
  ascFile.addNote("_");

  if (dataZone->m_entry.length()/link.m_fieldSize<link.m_N || link.m_fieldSize<=0 || link.m_N<=0) {
    MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: the position zone %d seems bad\n", dataZone->m_ids[0]));
    f << "Entries(LinkPos)[" << *dataZone << "]:" << link << "###,";
    input->setReadInverted(false);
    ascFile.addPos(dataZone->m_entry.begin());
    ascFile.addNote(f.str().c_str());
    return false;
  }
  for (int i=0; i<link.m_N; ++i) {
    long pos=input->tell();
    f.str("");
    if (i==0)
      f << "Entries(LinkPos)[" << *dataZone << "]:";
    else
      f << "LinkPos-" << i << ":";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    input->seek(pos+link.m_fieldSize, librevenge::RVNG_SEEK_SET);
  }
  if (input->tell()<dataZone->m_entry.end()) {
    f.str("");
    f << "LinkPos-:end";
    // check me: the size seems always a multiple of 16, so maybe reserved data...
    if (dataZone->m_entry.length()%link.m_fieldSize) {
      f << "###";
      static bool first=true;
      if (first) {
        MWAW_DEBUG_MSG(("RagTime5Text::readLinkZones: find some extra data\n"));
        first=false;
      }
    }
    ascFile.addPos(input->tell());
    ascFile.addNote(f.str().c_str());
  }
  input->setReadInverted(false);
  return true;
}


////////////////////////////////////////////////////////////
// PLC
////////////////////////////////////////////////////////////
bool RagTime5Text::readPLC(RagTime5TextInternal::ClusterText &cluster, int zoneId)
{
  auto zone=m_mainParser.getDataZone(zoneId);
  if (!zone || !zone->m_entry.valid() || (zone->m_entry.length()%6) ||
      zone->getKindLastPart(zone->m_kinds[1].empty())!="ItemData") {
    MWAW_DEBUG_MSG(("RagTime5Text::readPLC: the entry of zone %d seems bad\n", zoneId));
    return false;
  }
  MWAWEntry entry=zone->m_entry;
  MWAWInputStreamPtr input=zone->getInput();
  bool const hiLo=cluster.m_hiLoEndian;
  input->setReadInverted(!hiLo);
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);

  libmwaw::DebugFile &ascFile=zone->ascii();
  libmwaw::DebugStream f;
  zone->m_isParsed=true;
  ascFile.addPos(entry.end());
  ascFile.addNote("_");

  auto N=size_t(entry.length()/6);
  f << "Entries(TextPLCDef)[" << *zone << "]:";
  // first check the free list
  int freeId=cluster.m_plcDefFreeBegin;
  std::set<int> listFreeIds;
  bool ok=true;
  for (int i=0; i<cluster.m_plcDefNumFree; ++i) {
    if (freeId<=0 || freeId>int(N) || listFreeIds.find(freeId)!=listFreeIds.end()) {
      MWAW_DEBUG_MSG(("RagTime5Text::readPLC: find a bad freeId=%d\n", freeId));
      ok=false;
      break;
    }
    listFreeIds.insert(freeId);
    input->seek(entry.begin()+(freeId-1)*6, librevenge::RVNG_SEEK_SET);
    freeId=static_cast<int>(input->readLong(4));
  }
  if (ok && freeId) {
    MWAW_DEBUG_MSG(("RagTime5Text::readPLC: last free Id=%d seems bad\n", freeId));
  }
  if (!ok) {
    listFreeIds.clear();
    f << "###badFreeList,";
  }
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());

  cluster.m_PLCList.resize(N);
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  for (size_t i=0; i<N; ++i) {
    long pos=input->tell();
    if (listFreeIds.find(int(i+1))!=listFreeIds.end()) {
      ascFile.addPos(pos);
      ascFile.addNote("_");
      input->seek(6, librevenge::RVNG_SEEK_CUR);
      continue;
    }
    f.str("");
    f << "TextPLCDef-PLC" << i+1 << ":";
    RagTime5TextInternal::PLC plc;
    if (hiLo) {
      plc.m_fileType=static_cast<int>(input->readULong(2));
      plc.m_position=static_cast<int>(input->readULong(2));
      plc.m_value=static_cast<int>(input->readLong(2));
    }
    else {
      plc.m_value=static_cast<int>(input->readLong(2));
      plc.m_position=static_cast<int>(input->readULong(2));
      plc.m_fileType=static_cast<int>(input->readULong(2));
    }

    f << plc;
    cluster.m_PLCList[i]=plc;
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  input->setReadInverted(false);
  return true;
}

bool RagTime5Text::readPLCToCharStyle(RagTime5TextInternal::ClusterText &cluster)
{
  if (cluster.m_plcToStyleLink.m_ids.empty())
    return true;
  int const zoneId=cluster.m_plcToStyleLink.m_ids[0];
  if (!zoneId)
    return false;

  auto zone=m_mainParser.getDataZone(zoneId);
  if (!zone || !zone->m_entry.valid() || (zone->m_entry.length()%6) ||
      zone->getKindLastPart(zone->m_kinds[1].empty())!="ItemData") {
    MWAW_DEBUG_MSG(("RagTime5Text::readPLCToCharStyle: the entry of zone %d seems bad\n", zoneId));
    return false;
  }
  MWAWEntry entry=zone->m_entry;
  MWAWInputStreamPtr input=zone->getInput();
  input->setReadInverted(!cluster.m_hiLoEndian); // checkme: can also be zone->m_hiLoEndian
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);

  libmwaw::DebugFile &ascFile=zone->ascii();
  libmwaw::DebugStream f;
  zone->m_isParsed=true;
  ascFile.addPos(entry.end());
  ascFile.addNote("_");

  f << "Entries(TextPLCToCStyle)[" << *zone << "]:";

  auto N=int(entry.length()/6);
  if (N>cluster.m_plcToStyleLink.m_N) // rare but can happens
    N=cluster.m_plcToStyleLink.m_N;
  else if (N<cluster.m_plcToStyleLink.m_N) {
    MWAW_DEBUG_MSG(("RagTime5Text::readPLCToCharStyle: N value seems too short\n"));
    f << "##N=" << N << ",";
  }
  ascFile.addPos(entry.begin());
  ascFile.addNote(f.str().c_str());
  size_t numPLC=cluster.m_PLCList.size();
  long lastFindPos=-1;
  for (int i=0; i<N; ++i) {
    long pos=input->tell();
    f.str("");
    f << "TextPLCToCStyle-" << i << ":";
    auto id=size_t(input->readULong(4));
    auto styleId=static_cast<int>(input->readULong(2));
    f << "PLC" << id;
    if (id==0 || id>numPLC) {
      MWAW_DEBUG_MSG(("RagTime5Text::readPLCToCharStyle: find bad PLC id\n"));
      f << "###";
    }
    else {
      auto const plc=cluster.m_PLCList[size_t(id-1)];
      if ((i==0 && plc.m_position!=0) || (i && plc.m_position<lastFindPos)) {
        MWAW_DEBUG_MSG(("RagTime5Text::readPLCToCharStyle: the PLC position seems bad\n"));
        f << "###";
      }
      else
        cluster.m_posToStyleIdMap.insert(std::multimap<int, int>::value_type(plc.m_position, styleId));
      lastFindPos=plc.m_position;
      f << "[" << plc << "]";
    }
    f << "->TS" << styleId << ",";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  if (input->tell()!=entry.end()) {
    ascFile.addPos(input->tell());
    ascFile.addNote("TextPLCToCStyle:#extra");
  }
  input->setReadInverted(false);
  return true;
}

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

////////////////////////////////////////////////////////////
// interface send function
////////////////////////////////////////////////////////////

void RagTime5Text::flushExtra()
{
  for (auto it : m_state->m_idTextMap) {
    if (!it.second || it.second->m_isSent)
      continue;
    static bool first=true;
    if (first) {
      MWAW_DEBUG_MSG(("RagTime5Text::flushExtra: find some unseen zones: %d...\n", it.first));
      first=false;
    }
    send(*it.second, MWAWListenerPtr());
  }
}

bool RagTime5Text::send(RagTime5TextInternal::ClusterText &cluster, MWAWListenerPtr listener, int blockId, int block2Id)
{
  if (!listener)
    listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTime5Text::send: can not find the listener\n"));
    return false;
  }
  cluster.m_isSent=true;
  std::shared_ptr<RagTime5Zone> dataZone;
  int cId=!cluster.m_separatorLink.m_ids.empty() ? cluster.m_separatorLink.m_ids[0] : -1;
  if (cId>0)
    dataZone=m_mainParser.getDataZone(cId);
  std::vector<int> separators;
  if (!dataZone) {
    MWAW_DEBUG_MSG(("RagTime5Text::send: can not find the text separator zone %d\n", cId));
  }
  else
    readTextSeparators(*dataZone, separators);
  std::vector<RagTime5TextInternal::Block> const *blockZones=nullptr;
  std::vector<RagTime5TextInternal::Block> block2;
  if (block2Id>0 && block2Id<=static_cast<int>(cluster.m_block2List.size())) {
    block2.push_back(cluster.m_block2List[size_t(block2Id-1)]);
    blockZones=&block2;
  }
  else if (block2Id>0) {
    MWAW_DEBUG_MSG(("RagTime5Text::send: can not find the block %d in zone %d\n", block2Id, cluster.m_zoneId));
  }
  else if (blockId>0 && blockId<=static_cast<int>(cluster.m_blockList.size()) && !cluster.m_blockList[size_t(blockId-1)].empty())
    blockZones=&cluster.m_blockList[size_t(blockId-1)];
  else if (blockId>0) {
    MWAW_DEBUG_MSG(("RagTime5Text::send: can not find the block %d in zone %d\n", blockId, cluster.m_zoneId));
  }

  dataZone.reset();
  cId=!cluster.m_contentLink.m_ids.empty() ? cluster.m_contentLink.m_ids[0] : -1;
  if (cId>0)
    dataZone=m_mainParser.getDataZone(cId);
  else
    dataZone.reset();
  if (!dataZone || !dataZone->m_entry.valid() ||
      dataZone->getKindLastPart(dataZone->m_kinds[1].empty())!="Unicode") {
    MWAW_DEBUG_MSG(("RagTime5Text::send: can not find the text contents zone %d\n", cId));
    return false;
  }

  dataZone->m_isParsed=true;
  if (dataZone->m_entry.length()==0) return true;

  MWAWInputStreamPtr input=dataZone->getInput();
  libmwaw::DebugFile &ascFile=dataZone->ascii();
  libmwaw::DebugStream f;
  f << "Entries(TextUnicode)[" << *dataZone << "]:";
  if (dataZone->m_entry.length()%2) {
    MWAW_DEBUG_MSG(("RagTime5Text::send: bad length for zone %d\n", cId));
    f << "###";
    ascFile.addPos(dataZone->m_entry.begin());
    ascFile.addNote(f.str().c_str());
    ascFile.addPos(dataZone->m_entry.end());
    ascFile.addNote("_");
    return false;
  }
  input->setReadInverted(!cluster.m_hiLoEndian);
  input->seek(dataZone->m_entry.end()-2, librevenge::RVNG_SEEK_SET);
  if (input->readULong(2)==0xd00) {
    static bool first=true;
    if (first) {
      MWAW_DEBUG_MSG(("RagTime5Text::send: must change some hiLo\n"));
      first=false;
    }
    f << "###hiLo,";
    input->setReadInverted(cluster.m_hiLoEndian);
  }

  auto N=size_t(dataZone->m_entry.length()/2);
  size_t numSeparators=separators.size();
  size_t numZones=blockZones ? blockZones->size() : 1;
  if (N>numSeparators) {
    MWAW_DEBUG_MSG(("RagTime5Text::send: the separator list seems to short\n"));
    separators.resize(N,0);
    N=numSeparators;
  }
  ascFile.addPos(dataZone->m_entry.begin());
  ascFile.addNote(f.str().c_str());
  ascFile.addPos(dataZone->m_entry.end());
  ascFile.addNote("_");
  f.str("");
  f << "TextUnicode:";
  for (size_t z=0; z<numZones; ++z) {
    RagTime5TextInternal::Block block;
    bool checkBlock=false;;
    if (blockZones) {
      block=(*blockZones)[z];
      checkBlock=true;
    }
    else if (blockId<0) {
      if (-blockId>static_cast<int>(cluster.m_block2List.size())) {
        MWAW_DEBUG_MSG(("RagTime5Text::send: can not find block2 %d zone\n", -blockId));
        return true;
      }
      block=cluster.m_block2List[size_t(-blockId-1)];
      if (block.m_plc[0]==0 && block.m_plc[1]==0)
        return true;
      checkBlock=true;
    }

    size_t firstChar=0, lastChar=N;
    if (checkBlock) {
      bool ok=true;
      for (int i=0; i<2; ++i) {
        if (block.m_plc[i]==0) continue;
        if (block.m_plc[i]<0 || block.m_plc[i]>static_cast<int>(cluster.m_PLCList.size())) {
          MWAW_DEBUG_MSG(("RagTime5Text::send: find bad plc id for block %d\n", cluster.m_zoneId));
          ok=false;
          continue;
        }
        if (i==0)
          firstChar=size_t(cluster.m_PLCList[size_t(block.m_plc[i]-1)].m_position);
        else
          lastChar=size_t(cluster.m_PLCList[size_t(block.m_plc[i]-1)].m_position);
      }
      if (lastChar<firstChar) {
        MWAW_DEBUG_MSG(("RagTime5Text::send: find bad plc positions for block %d\n", cluster.m_zoneId));
        continue;
      }
      if (!ok) continue;
      if (lastChar>N) {
        MWAW_DEBUG_MSG(("RagTime5Text::send: last char seems too big for block %d\n", cluster.m_zoneId));
        lastChar=N;
      }

      auto plcIt=cluster.m_posToStyleIdMap.upper_bound(int(firstChar));
      MWAWFont font;
      MWAWParagraph para;
      if ((plcIt==cluster.m_posToStyleIdMap.end() || plcIt->first>static_cast<int>(firstChar)) &&
          plcIt !=cluster.m_posToStyleIdMap.begin() && m_styleManager->updateTextStyles((--plcIt)->second, font, para)) {
        listener->setParagraph(para);
        listener->setFont(font);
      }
    }
    input->seek(dataZone->m_entry.begin()+2*long(firstChar), librevenge::RVNG_SEEK_SET);
    long pos=input->tell();
    for (size_t i=firstChar; i<lastChar; ++i) {
      auto plcIt=cluster.m_posToStyleIdMap.lower_bound(int(i));
      while (plcIt!=cluster.m_posToStyleIdMap.end() && plcIt->first==static_cast<int>(i)) {
        int const styleId=plcIt++->second;
        f << "[TS" << styleId << "]";
        MWAWFont font;
        MWAWParagraph para;
        if (!m_styleManager->updateTextStyles(styleId, font, para)) {
          MWAW_DEBUG_MSG(("RagTime5Text::send: the style seems bad\n"));
          f << "###";
        }
        else {
          listener->setParagraph(para);
          listener->setFont(font);
        }
      }

      switch (separators[i]) {
      case 0: // none
      case 2: // sign separator: .,/-(x)
      case 3: // word separator
      case 4: // potential hyphenate
        break;
      default: // find also 1 and 7:link?, 8, 12
        f << "[m" << separators[i] << "]";
      }
      auto unicode=uint32_t(input->readULong(2));
      switch (unicode) {
      case 0:
        f << "###[0]";
        break;
      case 9:
        listener->insertTab();
        f << "\t";
        break;
      case 0xb:
      case 0xd:
        if (i+1==lastChar && z+1<numZones && unicode==0xd)
          break;
        listener->insertEOL(unicode==0xb);
        ascFile.addPos(pos);
        ascFile.addNote(f.str().c_str());
        pos=input->tell();
        f.str("");
        f << "TextUnicode:";
        break;
      case 0xe834: // end sub zone
      case 0xe835: // end zone
        f << "[" << std::hex << unicode << std::dec << "]";
        break;
      default:
        if (unicode<=0x1f) {
          MWAW_DEBUG_MSG(("RagTime5Text::send:  find an odd char %x\n", static_cast<unsigned int>(unicode)));
          f << "[#" << std::hex << unicode << std::dec << "]";
          break;
        }
        listener->insertUnicode(unicode);
        if (unicode<0x80)
          f << char(unicode);
        else
          f << "[" << std::hex << unicode << std::dec << "]";
        break;
      }
    }
    if (pos!=input->tell()||firstChar==lastChar) {
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
    }
  }
  ascFile.addPos(dataZone->m_entry.end());
  ascFile.addNote("_");
  input->setReadInverted(false);
  return true;
}

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

namespace RagTime5TextInternal
{


//! Internal: the helper to read a clustList
struct ClustListParser final : public RagTime5StructManager::DataParser {
  //! constructor
  ClustListParser(RagTime5ClusterManager &clusterManager, std::string const &zoneName)
    : RagTime5StructManager::DataParser(zoneName)
    , m_clusterList()
    , m_clusterManager(clusterManager)
  {
  }
  //! destructor
  ~ClustListParser() final;
  //! returns a name which can be used to 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();
    long fSz=endPos-pos;
    if (fSz!=10 && fSz!=12 && fSz!=14) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::ClustListParser::parse: bad data size\n"));
      return false;
    }

    std::vector<int> listIds;
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::ClustListParser::parse: can not read an cluster id\n"));
      f << "##clusterIds,";
      return false;
    }
    if (listIds[0]) {
      m_clusterList.push_back(listIds[0]);
      f << getClusterName(listIds[0]) << ",";
    }
    if (fSz==12 || fSz==14) {
      unsigned long lVal=input->readULong(4); // c00..small number
      f << "f0=" << (lVal&0x3fffffff);
      if ((lVal&0xc0000000)==0xc0000000) f << "*";
      else if (lVal&0xc0000000) f << ":" << (lVal>>30);
      f << ",";
    }
    int num=fSz==12 ? 2 : 3;
    for (int i=0; i<num; ++i) { // f3=1 if fSz==14, f1=0x200, f2=1 if fSz==12
      auto val=static_cast<int>(input->readLong(2));
      if (val) f << "f" << i+1 << "=" << val << ",";
    }
    return true;
  }

  //! the list of read cluster
  std::vector<int> m_clusterList;
private:
  //! 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 block 2 list
struct Block2ListParser final : public RagTime5StructManager::DataParser {
  //! constructor
  Block2ListParser()
    : RagTime5StructManager::DataParser("TextBlock2")
    , m_blockList()
  {
  }
  //! destructor
  ~Block2ListParser() 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!=20) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::Block2ListParser::parse: bad data size\n"));
      return false;
    }

    Block block;
    for (int &i : block.m_plc) i=static_cast<int>(input->readLong(4));
    if (block.m_plc[0]==0 && block.m_plc[1]==0) {
      f << "empty,";
      m_blockList.push_back(block);
      return true;
    }
    f << "PLC" << block.m_plc[0] << "<->" << block.m_plc[1] << ",";
    libmwaw::DebugStream f2;
    auto val=static_cast<int>(input->readULong(2));
    if (val) f2 << "fl=" << std::hex << val << std::dec << ",";
    for (int i=0; i<3; ++i) { // f0=a|1e, f1=1-e, f2=[02][145]
      val=static_cast<int>(input->readLong(2));
      if (val) f2 << "f" << i << "=" << val << ",";
    }
    for (int i=0; i<4; ++i) { // f3=1-30, f6=1-5c
      val=static_cast<int>(input->readLong(1));
      if (val) f2 << "f" << i+3 << "=" << val << ",";
    }
    f << f2.str();
    block.m_extra=f2.str();
    m_blockList.push_back(block);
    return true;
  }

  //! the list of block
  std::vector<Block> m_blockList;
private:
  //! copy constructor, not implemented
  Block2ListParser(Block2ListParser &orig) = delete;
  //! copy operator, not implemented
  Block2ListParser &operator=(Block2ListParser &orig) = delete;
};

Block2ListParser::~Block2ListParser()
{
}

//
//! low level: parser of text cluster
//
struct TextCParser final : public RagTime5ClusterManager::ClusterParser {
  //! constructor
  TextCParser(RagTime5ClusterManager &parser, int type, libmwaw::DebugFile &ascii)
    : ClusterParser(parser, type, "ClustText")
    , m_cluster(new ClusterText)
    , m_id(0)
    , m_actualZone(0)
    , m_numZones(0)
    , m_NToBlockIdMap()
    , m_what(-1)
    , m_linkId(-1)
    , m_fieldName("")
    , m_asciiFile(ascii)
  {
  }
  //! destructor
  ~TextCParser() final;
  //! return the current cluster
  std::shared_ptr<RagTime5ClusterManager::Cluster> getCluster() final
  {
    return m_cluster;
  }
  //! return the text cluster
  std::shared_ptr<ClusterText> getTextCluster()
  {
    return m_cluster;
  }
  //! start a new zone
  void startZone() final
  {
    ++m_id;
    if (m_what<=0)
      ++m_what;
    else if (m_what==1) {
      if (++m_actualZone>=m_numZones)
        ++m_what;
    }
  }
  //! end of a start zone call
  void endZone() final
  {
    if (m_link.empty())
      return;
    switch (m_linkId) {
    case 0:
      m_cluster->m_linkDefList.push_back(m_link);
      break;
    case 1:
    case 2:
      if (m_cluster->m_clusterLink[m_linkId-1].empty())
        m_cluster->m_clusterLink[m_linkId-1]=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::endZone: cluster link %d is already set\n", m_linkId));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 3:
      if (m_cluster->m_plcToStyleLink.empty())
        m_cluster->m_plcToStyleLink=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::endZone: link plcToTextStyle is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 4:
      if (m_cluster->m_plcDefLink.empty())
        m_cluster->m_plcDefLink=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::endZone: link plcDef is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    case 5:
      if (m_cluster->m_unknownLink[0].empty())
        m_cluster->m_unknownLink[0]=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::endZone: unknown link 0 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_linkId=-1;
    m_fieldName="";
    if (m_what==0)
      return parseHeaderZone(input,fSz,N,flag,f);
    /*
       normally the header is followed by num[zones] or less but sometimes block zone happens after other zones,
       so just test also fSz.
     */
    if (fSz==80) {
      m_what=1;
      return parseZoneBlock(input,fSz,N,flag,f);
    }
    if (N<0) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseZone: expected N value\n"));
      f << "###N=" << N << ",";
      return true;
    }
    m_what=2;
    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 0: // main
      if (field.m_type==RagTime5StructManager::Field::T_FieldList && field.m_fileType==0x15e0825) {
        for (auto const &child : field.m_fieldList) {
          if (child.m_type==RagTime5StructManager::Field::T_LongList && child.m_fileType==0xd7842) {
            if ((child.m_longList.size()%3)!=0) {
              MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: block def child seen bad\n"));
              f << "###blockDef[sz]=" << child.m_longList.size() << ",";
            }
            size_t N=child.m_longList.size()/3;
            m_cluster->m_blockList.resize(N);
            f << "blockDef=[";
            for (size_t b=0; b<N; ++b) {
              if (child.m_longList[3*b]==0) {
                f << "_,";
                continue;
              }
              if (m_NToBlockIdMap.find(static_cast<int>(child.m_longList[3*b]))!=m_NToBlockIdMap.end()) {
                MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: block pos is already set\n"));
                f << "#";
              }
              else
                m_NToBlockIdMap[static_cast<int>(child.m_longList[3*b])]=b;
              f << child.m_longList[3*b];
              for (size_t j=1; j<3; ++j) {
                if (child.m_longList[3*b+j]) f << ":" << child.m_longList[3*b+j];
                else f << ":_";
              }
              f << ",";
            }
            f << "],";
            continue;
          }
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: find unexpected unkn child[header]\n"));
          f << "####[" << child << "],";
        }
        break;
      }
      else if (field.m_type==RagTime5StructManager::Field::T_Long && field.m_fileType==0x3c057) {
        f << "unkn0=" << field.m_extra; // always 8|9
        break;
      }
      // extended header
      else if (field.m_type==RagTime5StructManager::Field::T_FieldList && field.m_fileType==0x15f9015) {
        f << "unknExt=[";
        for (auto const &child : field.m_fieldList) {
          if (child.m_type==RagTime5StructManager::Field::T_Unstructured && child.m_fileType==0xce017) {
            f << "unkn="<<child.m_extra << ",";
            continue;
          }
          if (child.m_type==RagTime5StructManager::Field::T_FieldList && child.m_fileType==0x15f6815) {
            for (auto const &child2 : child.m_fieldList) {
              if (child2.m_type==RagTime5StructManager::Field::T_Unstructured && child2.m_fileType==0xce017) {
                f << "unkn15f6815="<<child2.m_extra << ",";
                continue;
              }
              MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: find unexpected unkn child2[header]\n"));
              f << "###"<<child2.m_extra << ",";
            }
            continue;
          }
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: find unexpected unkn child[header]\n"));
          f << "####[" << child << "],";
        }
        f << "],";
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: find unexpected header field\n"));
      f << "###" << field;
      break;

    case 3: // linkdef
      if (field.m_type==RagTime5StructManager::Field::T_FieldList &&
          (field.m_fileType==0x15f4815 /* v5?*/ || field.m_fileType==0x160f815 /* v6? */)) {
        f << "decal=[";
        for (auto const &child : field.m_fieldList) {
          if (child.m_type==RagTime5StructManager::Field::T_LongList && child.m_fileType==0xce842) {
            for (auto val : child.m_longList)
              f << val << ",";
            m_link.m_longList=child.m_longList;
            continue;
          }
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: find unexpected decal child[list]\n"));
          f << "#[" << child << "],";
        }
        f << "],";
        break;
      }
      else if (field.m_type==RagTime5StructManager::Field::T_Unstructured && field.m_fileType==0xce017) {
        f << "unkn0=" << field.m_extra; // always 2: next value ?
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: find unexpected child[list]\n"));
      f << "###" << field;
      break;
    case 4: // 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;
      }
      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(("RagTime5TextInternal::TextCParser::parseField: find unexpected list link field\n"));
      f << "###" << field;
      break;
    case 5:
      if (field.m_type==RagTime5StructManager::Field::T_2Long && field.m_fileType==0x15e3017) {
        f << "unk=" << field.m_longValue[0] << "x" << field.m_longValue[1] << ",";
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseField: find unexpected plc link field\n"));
      f << "###" << field;
      break;
    default:
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::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)
  {
    f << "fl=" << std::hex << flag << std::dec << ",";
    long pos=input->tell();
    long endPos=pos+fSz-6;
    m_link.m_N=N;
    int val;
    long linkValues[4];
    std::string mess("");
    switch (fSz) {
    case 28: {
      val=static_cast<int>(input->readULong(2));
      if (val!=0x10) {
        f << "##fType=" << std::hex << val << std::dec << ",";
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: the field type seems bad\n"));
        return true;
      }
      m_fieldName="textZone";
      val=static_cast<int>(input->readULong(2));
      if (val!=4) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: the first value\n"));
        f << "##f0=" << val << ",";
      }
      val=static_cast<int>(input->readLong(2)); // always 0?
      if (val) f << "f1=" << val << ",";
      val=static_cast<int>(input->readLong(2)); // always f?
      if (val!=15) f << "f2=" << val << ",";
      std::vector<int> listIds;
      if (RagTime5StructManager::readDataIdList(input, 1, listIds) && listIds[0]) {
        if (!m_cluster->m_separatorLink.m_ids.empty()) {
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: oops the text separator is already set\n"));
          f << "###";
        }
        m_cluster->m_separatorLink.m_ids.push_back(static_cast<int>(listIds[0]));
        f << "textSep=data" << listIds[0] << "A,";
      }
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: can not read the text separator\n"));
        f << "##textSeparator,";
      }
      m_link.m_N=static_cast<int>(input->readULong(4));
      val=static_cast<int>(input->readLong(1)); // always 0?
      if (val) f << "f3=" << val << ",";
      listIds.clear();
      if (RagTime5StructManager::readDataIdList(input, 1, listIds) && listIds[0]) {
        if (!m_cluster->m_contentLink.m_ids.empty()) {
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: oops the text content is already set\n"));
          f << "###";
        }
        m_cluster->m_contentLink.m_ids.push_back(listIds[0]);
        f << "content=data" << listIds[0] << "A,";
      }
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: can not read the text content\n"));
        f << "##textContent,";
      }
      val=static_cast<int>(input->readLong(1)); // always 1?
      if (val) f << "f4=" << val << ",";
      f << m_link;
      break;
    }
    case 29: // never seen data
    case 32:
    case 34: // never seen data
    case 36:
    case 39:
    case 41:
    case 46: // never seen data
    case 52: {
      if (!readLinkHeader(input, fSz, m_link, linkValues, mess)) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: can not read the text content field\n"));
        f << "###link";
        return true;
      }
      m_what=4;
      long expectedFileType1=-1;
      if (m_link.m_fileType[1]==0x4f && m_link.m_fieldSize==6) { // fSz==34
        if (linkValues[3]!=0x15e4817) {
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: unexpected file type1\n"));
          f << "###type";
        }
        m_fieldName="plcToCStyle";
        m_linkId=3;
      }
      else if ((m_link.m_fileType[1]&0xFFD7)==0x50 && m_link.m_fieldSize==16) { // fSz=39
        if (linkValues[3]!=0x15f3817) {
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: unexpected file type1\n"));
          f << "###type";
        }
        m_linkId=0;
        m_what=3;
        m_fieldName=m_link.m_name="linkDef";
      }
      else if (m_link.m_fileType[1]==0x4f && m_link.m_fieldSize==14) { // fSz==39
        if (fSz!=39) {
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: unexpected field size\n"));
          f << "###fSz";
        }
        // two long and 2x0
        m_fieldName=m_link.m_name="textUnkn2";
        m_linkId=5;
      }
      else if (m_link.m_fileType[0]==0x3c052) // never seens data, find with fSz==29[fileType1==50],fSz==41|46[fileType1=40]
        m_fieldName="zone:longs2";
      else if (m_link.m_fileType[0]==0 && fSz==36) {
        expectedFileType1=0x10;
        // 00010021c000[zId?=0006]000000000001: clust link with size 14...
        m_fieldName="listClust";
        m_link.m_name="TextClustLst1";
        m_link.m_type=RagTime5ClusterManager::Link::L_ClusterLink;
        m_linkId=1;
      }
      else if (m_link.m_fileType[0]==0 && fSz==52 && m_link.m_fieldSize==6) {
        expectedFileType1=0;
        m_fieldName=m_link.m_name="plc";
        m_what=5;
        m_linkId=4;
      }
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: unexpected field\n"));
        m_fieldName="##unknown";
      }

      if (expectedFileType1>=0 && (m_link.m_fileType[1]&0xFFD7)!=expectedFileType1) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: fileType1 seems odd\n"));
        f << "###fileType1,";
      }
      f << m_link << "," << mess;
      long actPos=input->tell();
      if (actPos+4==endPos) {
        if (fSz==34) {
          val=static_cast<int>(input->readLong(4)); // always 1
          if (val!=1) f << "g0=" << val << ",";
        }
        else { // 36
          for (int i=0; i<2; ++i) { // small value between 3e and 74
            val=static_cast<int>(input->readLong(2));
            if (val) f << "g" << i << "=" << val << ",";
          }
        }
      }
      else if (actPos+9==endPos) { // fSz39
        for (int i=0; i<2; ++i) { // g0=1, g1=0|1|5
          val=static_cast<int>(input->readLong(4));
          if (val) f << "g" << i << "=" << val << ",";
        }
        val=static_cast<int>(input->readLong(1)); // always 0
        if (val) f << "g2=" << val << ",";
      }
      else if (actPos+13==endPos || actPos+18==endPos) { // fSz41|46
        val=static_cast<int>(input->readLong(1)); // always 1
        if (val!=1) f << "g0=" << val << ",";
        if (actPos+18==endPos) {
          for (int i=0; i<5; ++i) { // only 0
            val=static_cast<int>(input->readLong(1));
            if (val) f << "h" << i << "=" << val << ",";
          }
        }
        for (int i=0; i<3; ++i) { // g1=1, g3=g2+1
          val=static_cast<int>(input->readLong(4));
          if (val) f << "g" << i+1 << "=" << val << ",";
        }
      }
      else if (actPos+22==endPos) { // fSz==52, plc
        for (int i=0; i<5; ++i) { // g2=0 maybe an 2xint other small number
          val=static_cast<int>(input->readLong(4));
          switch (i) {
          case 0:
            if (m_cluster->m_plcDefFreeBegin) {
              MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: the plc root is already set\n"));
              f << "###";
            }
            else
              m_cluster->m_plcDefFreeBegin=val;
            f << "free[rootId]=" << val << ",";
            break;
          case 4:
            if (m_cluster->m_plcDefNumFree<0)
              m_cluster->m_plcDefNumFree=val;
            f << "free[num]=" << val << ",";
            break;
          default:
            if (val) f << "g" << i << "=" << val << ",";
            break;
          }
        }
        val=static_cast<int>(input->readLong(2)); // always 1
        if (val!=1)
          f << "g5=" << val << ",";
      }
      break;
    }
    case 49: { // checkme, seens rarely with no data...
      for (int i=0; i<6; ++i) { // f3=1, f4=1c
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readULong(4));
      if (val!=0x15e0842) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: fileType0 seems odd\n"));
        f << "###fileType0=" << std::hex << val << std::dec << ",";
      }
      for (int i=0; i<3; ++i) { // f6=1, f7=1|2
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i+6 << "=" << val << ",";
      }
      float dim[4];
      for (auto &d : dim) d=float(input->readLong(4))/65536.f;
      f << "dim?=" << MWAWBox2f(MWAWVec2f(dim[0],dim[1]), MWAWVec2f(dim[2],dim[3])) << ",";
      for (int i=0; i<2; ++i) { // g1=2|3
        val=static_cast<int>(input->readLong(2));
        if (val) f << "g" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readLong(1)); // always 0?
      if (val) f << "g2=" << val << ",";
      break;
    }
    case 69:
    case 71: {
      if (!readLinkHeader(input, fSz, m_link, linkValues, mess)) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: can not read the link position field\n"));
        f << "###link";
        return true;
      }
      if (linkValues[3]==0x15f3817) {
        if ((m_link.m_fileType[1]&0xFFF7)!=0x43 && (m_link.m_fileType[1]&0xFFF7)!=0x50) {
          MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: fileType1 seems odd\n"));
          f << "###fileType1,";
        }
        m_linkId=0;
        m_what=3;
        m_fieldName=m_link.m_name="linkDef";
      }
      else {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: unexpected field\n"));
        m_fieldName="##unknown";
      }
      f << m_link << "," << mess;
      for (int i=0; i<2; ++i) { // g0=1, g1=2,b,c
        val=static_cast<int>(input->readLong(4));
        if (val) f << "g" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readLong(1)); // always 0
      if (val) f << "g2=" << val << ",";
      val=static_cast<int>(input->readLong(2)); // always 0
      if (val!=0x10) f << "g3=" << val << ",";
      val=static_cast<int>(input->readLong(4)); // 1,3, 5
      if (val) f << "g3=" << val << ",";
      RagTime5ClusterManager::Link link2;
      mess="";
      if (!readLinkHeader(input, fSz, link2, linkValues, mess)) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: can not read the link second field\n"));
        f << "###link2";
        return true;
      }
      if (fSz==69 && link2.m_fieldSize==12)
        m_cluster->m_clusterLink[1]=link2;
      else if (fSz==71 && link2.m_ids.size()==2) {
        // FIXME: store directly the field pos and set link2 as main link
        m_link.m_ids.push_back(link2.m_ids[0]);
        m_link.m_ids.push_back(link2.m_ids[1]);
      }
      else if (!link2.empty()) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseDataZone: can not find the second link field\n"));
        f << "###";
        m_cluster->m_linksList.push_back(link2);
      }
      f << "link2=[" << link2 << "]," << mess;
      break;
    }
    default:
      f << "###fSz";
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseHeaderZone: find unexpected field size\n"));
      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";
    if (N!=-5 || m_dataId!=0 || (fSz!=135 && fSz!=140 && fSz!=143 && fSz!=208 && fSz!=212 && fSz!=213 && fSz!=216)) {
      f << "###N=" << N << ",fSz=" << fSz << ",";
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseHeaderZone: find unexpected main field\n"));
      return true;
    }
    bool hasData1=(fSz==140||fSz==213);
    int numData2=(fSz==143||fSz==216) ? 4 : fSz==212 ? 2 : 0;
    int val;
    for (int i=0; i<2; ++i) { // always 0?
      val=static_cast<int>(input->readLong(2));
      if (val) f << "f" << i << "=" << 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(("RagTime5TextInternal::TextCParser::parseHeaderZone: unexpected zone type\n"));
      f << "##zoneType=" << std::hex << val << std::dec << ",";
    }
    for (int i=0; i<2; ++i) { // f2=9-5d, f3=0
      val=static_cast<int>(input->readLong(4));
      if (val)
        f << "f" << i+2 << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(1)); // 0|1
    if (val)
      f << "fl=" << val << ",";
    val=static_cast<int>(input->readULong(2));
    if (val) // [08]0[08][049]
      f << "fl2=" << std::hex << val << std::dec << ",";
    val=static_cast<int>(input->readLong(1)); // 1|1d
    if (val!=1)
      f << "fl3=" << val << ",";
    val=static_cast<int>(input->readULong(2)); // alway 10
    if (val!=0x10)
      f << "f4=" << val << ",";
    m_numZones=static_cast<int>(input->readLong(4));
    if (m_numZones)
      f << "num[zones]=" << m_numZones << ",";
    for (int i=0; i<11; ++i) { // g8=40|60
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "g" << i << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(1)); // always 1
    if (val!=1)
      f << "fl4=" << val << ",";
    if (hasData1) {
      for (int i=0; i<5; ++i) { // unsure find only 0 here
        val=static_cast<int>(input->readLong(1));
        if (val)
          f << "flA" << i << "=" << val << ",";
      }
    }

    for (int i=0; i<2; ++i) { // always 1,2
      val=static_cast<int>(input->readLong(4));
      if (val!=i+1)
        f << "h" << i << "=" << val << ",";
    }
    for (int i=0; i<2; ++i) { // always 0,4
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "h" << i+2 << "=" << val << ",";
    }
    for (int i=0; i<4; ++i) { // always h4=3, h5=small number, h6=h5+1
      val=static_cast<int>(input->readLong(4));
      if (val)
        f << "h" << i+4 << "=" << val << ",";
    }
    for (int i=0; i<2; ++i) {  // always 1,4
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "h" << i+8 << "=" << val << ",";
    }
    val=static_cast<int>(input->readULong(4));
    if (val!=0x5555)
      f << "#fileType=" << std::hex << val << std::dec << ",";
    val=static_cast<int>(input->readULong(4));
    if (val!=0x18000)
      f << "#fileType2=" << std::hex << val << std::dec << ",";
    for (int i=0; i<5; ++i) { // always 0
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "j" << i << "=" << val << ",";
    }
    for (int i=0; i<5; ++i) { // j5=0|5, j6=0|5, j7=small number, j8=0|5
      val=static_cast<int>(input->readLong(4));
      if (val)
        f << "j" << i+5 << "=" << val << ",";
    }
    f << "IDS=[";
    for (int i=0; i<2; ++i) // unsure, junk
      f << std::hex << input->readULong(4) << std::dec << ",";
    f << "],";
    val=static_cast<int>(input->readULong(2)); // c00|cef
    if (val)
      f << "fl5=" << std::hex << val << std::dec << ",";
    for (int i=0; i<numData2; ++i) { // always 0
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "k" << i << "=" << val << ",";
    }
    if (fSz<=143)
      return true;

    f << "link2=[";
    long linkValues[4];
    std::string mess("");
    val=static_cast<int>(input->readULong(2));
    if (val!=0x10) f << "fl=" << std::hex << val << std::dec << ",";
    RagTime5ClusterManager::Link link2;
    link2.m_N=static_cast<int>(input->readLong(4));
    mess="";
    if (!readLinkHeader(input, fSz, link2, linkValues, mess)) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseHeaderZone: can not read the second link\n"));
      f << "###link2";
      return true;
    }
    if (linkValues[3]==0x15f3817 && link2.m_fieldSize==20)
      m_cluster->m_block2ToPlcLink=link2;
    else {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseHeaderZone: block2 to plc link\n"));
      f << "###";
    }
    f << link2 << "," << mess;
    for (int i=0; i<2; ++i) { // always 1 and 4
      val=static_cast<int>(input->readLong(4));
      if (val) f << "f" << i << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(1)); // always 1
    if (val) f << "f2=" << val << ",";
    f << "],";

    f << "link3=[";
    mess="";
    val=static_cast<int>(input->readULong(2));
    if (val!=0x10) f << "fl=" << std::hex << val << std::dec << ",";
    RagTime5ClusterManager::Link link3;
    link3.m_N=static_cast<int>(input->readLong(4));
    mess="";
    if (!readLinkHeader(input, fSz, link3, linkValues, mess)) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseHeaderZone: can not read the third link\n"));
      f << "###link3";
      return true;
    }
    if (link3.m_fieldSize==12)
      m_cluster->m_unknownLink[1]=link3;
    else {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseHeaderZone: third link seems bad\n"));
      f << "###";
    }
    f << link3 << "," << mess;
    f << "],";

    std::vector<int> listIds;
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseHeaderZone: can not read an cluster id\n"));
      f << "##clusterIds,";
      return false;
    }
    if (listIds[0]) {
      m_cluster->m_clusterIdsList.push_back(listIds[0]);
      f << "cluster=" << getClusterName(listIds[0]) << ",";
    }
    return true;
  }
  //! parse a zone block
  bool parseZoneBlock(MWAWInputStreamPtr &input, long fSz, int N, int flag, libmwaw::DebugStream &f)
  {
    if (N<0 || m_what!=1 || (fSz!=80)) {
      f << "###N=" << N << ",fSz=" << fSz << ",";
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseZoneBlock: find unexpected main field\n"));
      return false;
    }
    RagTime5TextInternal::Block block;
    m_fieldName="block";
    std::string debugHeader=f.str();
    f.str("");
    if (N!=1) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseZoneBlock: zone N seems badA\n"));
      f << "#N=" << N << ",";
    }
    auto val=static_cast<int>(input->readULong(2)); // always 0?
    if (val) f << "f0=" << val << ",";
    block.m_id=static_cast<int>(input->readULong(2));
    val=static_cast<int>(input->readULong(2)); //[04][01248a][01][23]
    if (val) f << "fl=" << std::hex << val << std::dec << ",";
    block.m_subId=static_cast<int>(input->readULong(2));
    val=static_cast<int>(input->readULong(2)); //f1=0|3ffe
    if (val) f << "f1=" << val << ",";
    float dim[4];
    for (auto &d : dim) d=float(input->readLong(4))/65536.f;
    block.m_dimension=MWAWBox2f(MWAWVec2f(dim[0],dim[1]), MWAWVec2f(dim[2],dim[3]));
    for (float &i : dim) i=float(input->readLong(4))/65536.f;
    MWAWBox2f box2(MWAWVec2f(dim[0],dim[1]), MWAWVec2f(dim[2],dim[3]));
    if (block.m_dimension!=box2)
      f << "boxA=" << box2 << ",";
    int nextId=0;
    for (int i=0; i<6; ++i) { // g1=0|2, g3=9|7
      val=static_cast<int>(input->readLong(2));
      if (!val) continue;
      switch (i) {
      case 1: // prev
        f << "prev=ClustText-" << val-1 << ",";
        break;
      case 3: // next
        f << "next=ClustText-" << val-1 << ",";
        nextId=val;
        break;
      default:
        f << "g" << i << "=" << val << ",";
        break;
      }
    }
    for (int &i : block.m_plc) i=static_cast<int>(input->readULong(4));
    for (int i=0; i<6; ++i) { // h1=h2=0|-1
      val=static_cast<int>(input->readLong(2));
      if (val) f << "h" << i << "=" << val << ",";
    }
    block.m_extra=f.str();
    f.str("");
    f << debugHeader << "block,fl=" << std::hex << flag << std::dec << "," << block;

    if (m_NToBlockIdMap.find(m_id)==m_NToBlockIdMap.end()) {
      MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseZoneBlock: unknown block for N=%d\n", m_id));
      f << "###unknown,";
    }
    else {
      m_cluster->m_blockList[m_NToBlockIdMap.find(m_id)->second].push_back(block);
      if (nextId && m_NToBlockIdMap.find(nextId)!=m_NToBlockIdMap.end()) {
        MWAW_DEBUG_MSG(("RagTime5TextInternal::TextCParser::parseZoneBlock: next id block for N=%d is already set\n", nextId));
        f << "###nextId,";
      }
      else if (nextId)
        m_NToBlockIdMap[nextId]=m_NToBlockIdMap.find(m_id)->second;
    }
    return true;
  }

  //! the current cluster
  std::shared_ptr<ClusterText> m_cluster;
  //! the block id
  int m_id;
  //! the actual zone
  int m_actualZone;
  //! the number of zones
  int m_numZones;
  //! the field pos to block map
  std::map<int, size_t> m_NToBlockIdMap;
  //! a index to know which field is parsed :  0: main, 1: list zones, 3: linkdef, 4: list, 5; text unkn1
  int m_what;
  //! the link id: 0: linkdef, 1-2: cluster list, 3: text unknown, 4: unkA list, 5:unkn fixed fSz==14
  int m_linkId;
  //! the actual field name
  std::string m_fieldName;
  //! the ascii file
  libmwaw::DebugFile &m_asciiFile;
private:
  //! copy constructor (not implemented)
  TextCParser(TextCParser const &orig) = delete;
  //! copy operator (not implemented)
  TextCParser &operator=(TextCParser const &orig) = delete;
};

TextCParser::~TextCParser()
{
}

}

std::shared_ptr<RagTime5ClusterManager::Cluster> RagTime5Text::readTextCluster(RagTime5Zone &zone, int zoneType)
{
  auto clusterManager=m_mainParser.getClusterManager();
  if (!clusterManager) {
    MWAW_DEBUG_MSG(("RagTime5Text::readTextCluster: oops can not find the cluster manager\n"));
    return std::shared_ptr<RagTime5ClusterManager::Cluster>();
  }
  RagTime5TextInternal::TextCParser parser(*clusterManager, zoneType, zone.ascii());
  if (!clusterManager->readCluster(zone, parser) || !parser.getTextCluster()) {
    MWAW_DEBUG_MSG(("RagTime5Text::readTextCluster: oops can not find the cluster\n"));
    return std::shared_ptr<RagTime5ClusterManager::Cluster>();
  }
  auto cluster=parser.getTextCluster();
  if (m_state->m_idTextMap.find(zone.m_ids[0])!=m_state->m_idTextMap.end()) {
    MWAW_DEBUG_MSG(("RagTime5Text::readTextCluster: oops text zone %d is already stored\n", zone.m_ids[0]));
  }
  else
    m_state->m_idTextMap[zone.m_ids[0]]=cluster;
  m_mainParser.checkClusterList(cluster->m_clusterIdsList);

  if (!cluster->m_dataLink.empty()) {
    MWAW_DEBUG_MSG(("RagTime5Text::readTextCluster: oops do not know how to read the dataLink\n"));
  }

  // the text<->separator zone cluster->m_separatorLink.m_ids[0] when we send the cluster
  // the textzone cluster->m_contentLink.m_ids[0] will be parsed when we send the cluster

  if (!cluster->m_plcDefLink.m_ids.empty())
    readPLC(*cluster, cluster->m_plcDefLink.m_ids[0]);
  readPLCToCharStyle(*cluster); // read cluster->m_plcToStyleLink
  if (!cluster->m_block2ToPlcLink.empty()) {
    RagTime5TextInternal::Block2ListParser block2Parser;
    m_mainParser.readFixedSizeZone(cluster->m_block2ToPlcLink, block2Parser);
    cluster->m_block2List=block2Parser.m_blockList;
  }
  if (!cluster->m_unknownLink[0].empty())
    m_mainParser.readFixedSizeZone(cluster->m_unknownLink[0], "TextUnkn0");
  if (!cluster->m_unknownLink[1].empty())
    m_mainParser.readFixedSizeZone(cluster->m_unknownLink[1], "TextUnkn1");
  for (int i=0; i<2; ++i) {
    if (cluster->m_clusterLink[i].empty()) continue;
    RagTime5TextInternal::ClustListParser linkParser(*clusterManager, i==0 ? "TextClustLst1" : "TextClustLst2");
    if (i==0)
      m_mainParser.readListZone(cluster->m_clusterLink[i], linkParser);
    else {
      // argh m_N is not set, we must set it
      std::shared_ptr<RagTime5Zone> linkZone;
      if (!cluster->m_clusterLink[i].m_ids.empty())
        linkZone=m_mainParser.getDataZone(cluster->m_clusterLink[i].m_ids[0]);
      if (linkZone && !linkZone->m_entry.valid()) // rare but can happens
        continue;
      if (!linkZone || (linkZone->m_entry.length()%12) ||
          linkZone->getKindLastPart(linkZone->m_kinds[1].empty())!="ItemData") {
        MWAW_DEBUG_MSG(("RagTime5Text::readTextCluster: the cluster2 zone of %d seems bad\n",
                        static_cast<int>(cluster->m_zoneId)));
      }
      else
        cluster->m_clusterLink[i].m_N=int(linkZone->m_entry.length()/12);
      m_mainParser.readFixedSizeZone(cluster->m_clusterLink[i], linkParser);
    }
    m_mainParser.checkClusterList(linkParser.m_clusterList);
  }
  for (auto const &lnk : cluster->m_linkDefList)
    readLinkZones(*cluster, lnk);
  for (auto const &link : cluster->m_linksList) {
    if (link.m_type==RagTime5ClusterManager::Link::L_List) {
      m_mainParser.readListZone(link);
      continue;
    }
    std::stringstream s;
    s << "Text_Data" << link.m_fieldSize;
    RagTime5StructManager::DataParser defaultParser(link.m_name.empty() ? s.str() : link.m_name);
    m_mainParser.readFixedSizeZone(link, defaultParser);
  }
  return cluster;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: