File indexing completed on 2024-12-22 03:35:49

0001 /*
0002     File                 : ROOTFilter.cpp
0003     Project              : LabPlot
0004     Description          : ROOT(CERN) I/O-filter
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2018 Christoph Roick <chrisito@gmx.de>
0007     SPDX-FileCopyrightText: 2018-2022 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "backend/datasources/filters/ROOTFilter.h"
0012 #include "backend/core/column/Column.h"
0013 #include "backend/datasources/filters/ROOTFilterPrivate.h"
0014 #include "backend/lib/XmlStreamReader.h"
0015 #include "backend/lib/macros.h"
0016 #include "backend/spreadsheet/Spreadsheet.h"
0017 
0018 #include <KLocalizedString>
0019 
0020 #include <QFileInfo>
0021 #include <QStack>
0022 
0023 #include <cmath>
0024 #include <fstream>
0025 
0026 #ifdef HAVE_ZIP
0027 #include <lz4.h>
0028 #include <zlib.h>
0029 #endif
0030 
0031 ROOTFilter::ROOTFilter()
0032     : AbstractFileFilter(FileType::ROOT)
0033     , d(new ROOTFilterPrivate) {
0034 }
0035 
0036 ROOTFilter::~ROOTFilter() = default;
0037 
0038 void ROOTFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) {
0039     d->readDataFromFile(fileName, dataSource, importMode);
0040 }
0041 
0042 void ROOTFilter::write(const QString& fileName, AbstractDataSource* dataSource) {
0043     d->write(fileName, dataSource);
0044 }
0045 
0046 void ROOTFilter::setCurrentObject(const QString& object) {
0047     d->currentObject = object;
0048 }
0049 
0050 const QString ROOTFilter::currentObject() const {
0051     return d->currentObject;
0052 }
0053 
0054 ROOTFilter::Directory ROOTFilter::listHistograms(const QString& fileName) const {
0055     return d->listHistograms(fileName);
0056 }
0057 
0058 ROOTFilter::Directory ROOTFilter::listTrees(const QString& fileName) const {
0059     return d->listTrees(fileName);
0060 }
0061 
0062 QVector<QStringList> ROOTFilter::listLeaves(const QString& fileName, qint64 pos) const {
0063     return d->listLeaves(fileName, pos);
0064 }
0065 
0066 QVector<QStringList> ROOTFilter::previewCurrentObject(const QString& fileName, int first, int last) const {
0067     return d->previewCurrentObject(fileName, first, last);
0068 }
0069 
0070 int ROOTFilter::rowsInCurrentObject(const QString& fileName) const {
0071     return d->rowsInCurrentObject(fileName);
0072 }
0073 
0074 void ROOTFilter::setStartRow(const int s) {
0075     d->startRow = s;
0076 }
0077 
0078 int ROOTFilter::startRow() const {
0079     return d->startRow;
0080 }
0081 
0082 void ROOTFilter::setEndRow(const int e) {
0083     d->endRow = e;
0084 }
0085 
0086 int ROOTFilter::endRow() const {
0087     return d->endRow;
0088 }
0089 
0090 void ROOTFilter::setColumns(const QVector<QStringList>& columns) {
0091     d->columns = columns;
0092 }
0093 
0094 QVector<QStringList> ROOTFilter::columns() const {
0095     return d->columns;
0096 }
0097 
0098 void ROOTFilter::save(QXmlStreamWriter* writer) const {
0099     writer->writeStartElement(QStringLiteral("rootFilter"));
0100     writer->writeAttribute(QStringLiteral("object"), d->currentObject);
0101     writer->writeAttribute(QStringLiteral("startRow"), QString::number(d->startRow));
0102     writer->writeAttribute(QStringLiteral("endRow"), QString::number(d->endRow));
0103     for (const auto& c : d->columns) {
0104         writer->writeStartElement(QStringLiteral("column"));
0105         for (const auto& s : c)
0106             writer->writeTextElement(QStringLiteral("id"), s);
0107         writer->writeEndElement();
0108     }
0109     writer->writeEndElement();
0110 }
0111 
0112 bool ROOTFilter::load(XmlStreamReader* reader) {
0113     QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used");
0114     QXmlStreamAttributes attribs = reader->attributes();
0115 
0116     // read attributes
0117     d->currentObject = attribs.value(QStringLiteral("object")).toString();
0118     if (d->currentObject.isEmpty())
0119         reader->raiseWarning(attributeWarning.arg(QStringLiteral("object")));
0120 
0121     QString str = attribs.value(QStringLiteral("startRow")).toString();
0122     if (str.isEmpty())
0123         reader->raiseWarning(attributeWarning.arg(QStringLiteral("startRow")));
0124     else
0125         d->startRow = str.toInt();
0126 
0127     str = attribs.value(QStringLiteral("endRow")).toString();
0128     if (str.isEmpty())
0129         reader->raiseWarning(attributeWarning.arg(QStringLiteral("endRow")));
0130     else
0131         d->endRow = str.toInt();
0132 
0133     d->columns.clear();
0134     while (reader->readNextStartElement()) {
0135         if (reader->name() == QLatin1String("column")) {
0136             QStringList c;
0137             while (reader->readNextStartElement()) {
0138                 if (reader->name() == QLatin1String("id"))
0139                     c << reader->readElementText();
0140                 else
0141                     reader->skipCurrentElement();
0142             }
0143             if (!c.empty())
0144                 d->columns << c;
0145         } else
0146             reader->skipCurrentElement();
0147     }
0148     if (d->columns.empty())
0149         reader->raiseWarning(i18n("No column available"));
0150 
0151     return true;
0152 }
0153 
0154 /**************** ROOTFilterPrivate implementation *******************/
0155 
0156 ROOTFilterPrivate::ROOTFilterPrivate() = default;
0157 
0158 ROOTFilterPrivate::FileType ROOTFilterPrivate::currentObjectPosition(const QString& fileName, long int& pos) {
0159     QStringList typeobject = currentObject.split(QLatin1Char(':'));
0160     if (typeobject.size() < 2)
0161         return FileType::Invalid;
0162 
0163     FileType type;
0164     if (typeobject.first() == QStringLiteral("Hist"))
0165         type = FileType::Hist;
0166     else if (typeobject.first() == QStringLiteral("Tree"))
0167         type = FileType::Tree;
0168     else
0169         return FileType::Invalid;
0170 
0171     typeobject.removeFirst();
0172     QStringList path = typeobject.join(QLatin1Char(':')).split(QLatin1Char('/'));
0173     ROOTFilter::Directory dir = type == FileType::Hist ? listHistograms(fileName) : listTrees(fileName);
0174     const ROOTFilter::Directory* node = &dir;
0175     while (path.size() > 1) {
0176         bool next = false;
0177         for (const auto& child : node->children) {
0178             if (child.name == path.first()) {
0179                 node = &child;
0180                 path.pop_front();
0181                 next = true;
0182                 break;
0183             }
0184         }
0185         if (!next)
0186             return FileType::Invalid;
0187     }
0188     for (const auto& child : node->content) {
0189         if (child.first == path.first()) {
0190             pos = child.second;
0191             break;
0192         }
0193     }
0194     return type;
0195 }
0196 
0197 void ROOTFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) {
0198     DEBUG(Q_FUNC_INFO << ", object: " << STDSTRING(currentObject));
0199 
0200     long int pos = 0;
0201     const auto type = currentObjectPosition(fileName, pos);
0202     if (pos == 0) // is not changed???
0203         return;
0204 
0205     DEBUG("start/end row = " << startRow << " " << endRow)
0206 
0207     if (type == FileType::Hist) {
0208         auto bins = readHistogram(pos);
0209         const int nbins = static_cast<int>(bins.size());
0210 
0211         // skip underflow and overflow bins by default
0212         int first = std::max(std::abs(startRow), 0);
0213         int last = endRow < 0 ? nbins - 1 : std::max(first - 1, std::min(endRow, nbins - 1));
0214 
0215         DEBUG("first/last = " << first << " " << last)
0216 
0217         QStringList headers;
0218         for (const auto& l : columns) {
0219             headers << l.last();
0220         }
0221 
0222         std::vector<void*> dataContainer;
0223         const int columnOffset = dataSource->prepareImport(dataContainer,
0224                                                            importMode,
0225                                                            last - first + 1,
0226                                                            columns.size(),
0227                                                            headers,
0228                                                            QVector<AbstractColumn::ColumnMode>(columns.size(), AbstractColumn::ColumnMode::Double));
0229 
0230         // read data
0231         DEBUG(" reading " << last - first + 1 << " lines");
0232 
0233         int c = 0;
0234         auto* spreadsheet = dynamic_cast<Spreadsheet*>(dataSource);
0235 
0236         for (const auto& l : columns) {
0237             QVector<double>& container = *static_cast<QVector<double>*>(dataContainer[c]);
0238             if (l.first() == QStringLiteral("center")) {
0239                 if (spreadsheet)
0240                     spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::X);
0241                 for (int i = first; i <= last; ++i)
0242                     container[i - first] = (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge)
0243                         : i == 0                                    ? bins.front().lowedge // -infinity
0244                                                                     : -bins.front().lowedge; // +infinity
0245             } else if (l.first() == QStringLiteral("low")) {
0246                 if (spreadsheet)
0247                     spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::X);
0248                 for (int i = first; i <= last; ++i)
0249                     container[i - first] = bins[i].lowedge;
0250             } else if (l.first() == QStringLiteral("content")) {
0251                 if (spreadsheet)
0252                     spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0253                 for (int i = first; i <= last; ++i)
0254                     container[i - first] = bins[i].content;
0255             } else if (l.first() == QStringLiteral("error")) {
0256                 if (spreadsheet)
0257                     spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::YError);
0258                 for (int i = first; i <= last; ++i)
0259                     container[i - first] = std::sqrt(bins[i].sumw2);
0260             }
0261             ++c;
0262         }
0263 
0264         dataSource->finalizeImport(columnOffset, 0, columns.size() - 1, QString(), importMode);
0265     } else if (type == FileType::Tree) {
0266         const int nentries = static_cast<int>(currentROOTData->treeEntries(pos));
0267 
0268         int first = std::max(std::abs(startRow), 0);
0269         int last = std::max(first - 1, std::min(endRow, nentries - 1));
0270 
0271         DEBUG("first/last = " << first << " " << last << ", nentries = " << nentries)
0272 
0273         QStringList headers;
0274         for (const auto& l : columns) {
0275             QString lastelement = l.back();
0276             bool isArray = false;
0277             if (lastelement.at(0) == QLatin1Char('[') && lastelement.at(lastelement.size() - 1) == QLatin1Char(']')) {
0278                 lastelement.mid(1, lastelement.length() - 2).toUInt(&isArray);
0279             }
0280             if (!isArray || l.count() == 2)
0281                 headers << l.join(isArray ? QString() : QLatin1String(":"));
0282             else
0283                 headers << l.first() + QLatin1Char(':') + l.at(1) + l.back();
0284         }
0285 
0286         std::vector<void*> dataContainer;
0287         const int columnOffset = dataSource->prepareImport(dataContainer,
0288                                                            importMode,
0289                                                            last - first + 1,
0290                                                            columns.size(),
0291                                                            headers,
0292                                                            QVector<AbstractColumn::ColumnMode>(columns.size(), AbstractColumn::ColumnMode::Double));
0293 
0294         int c = 0;
0295         for (const auto& l : columns) {
0296             // DEBUG("column " << c)
0297             unsigned int element = 0;
0298             QString lastelement = l.back(), leaf = l.front();
0299             bool isArray = false;
0300             if (lastelement.at(0) == QLatin1Char('[') && lastelement.at(lastelement.size() - 1) == QLatin1Char(']')) {
0301                 element = lastelement.mid(1, lastelement.length() - 2).toUInt(&isArray);
0302                 if (!isArray)
0303                     element = 0;
0304                 if (l.count() > 2)
0305                     leaf = l.at(1);
0306             } else if (l.count() > 1)
0307                 leaf = l.at(1);
0308 
0309             QVector<double>& container = *static_cast<QVector<double>*>(dataContainer[c++]);
0310             auto data = readTree(pos, l.first(), leaf, (int)element, last);
0311             // QDEBUG("DATA = " << data)
0312             for (int i = first; i <= last; ++i)
0313                 container[i - first] = data[i];
0314         }
0315 
0316         dataSource->finalizeImport(columnOffset, 0, columns.size() - 1, QString(), importMode);
0317     }
0318 }
0319 
0320 void ROOTFilterPrivate::write(const QString& /*fileName*/, AbstractDataSource* /*dataSource*/) {
0321 }
0322 
0323 ROOTFilter::Directory ROOTFilterPrivate::listContent(const std::map<long int, ROOTData::Directory>& dataContent, std::string (ROOTData::*nameFunc)(long int)) {
0324     ROOTFilter::Directory dirs;
0325     QHash<const std::remove_reference<decltype(dataContent)>::type::value_type*, ROOTFilter::Directory*> filledDirs;
0326     for (const auto& path : dataContent) {
0327         if (!path.second.content.empty()) {
0328             QStack<decltype(filledDirs)::key_type> addpath;
0329             auto pos = &path;
0330             ROOTFilter::Directory* currentdir = &dirs;
0331             while (true) {
0332                 auto it = filledDirs.find(pos);
0333                 if (it != filledDirs.end()) {
0334                     currentdir = it.value();
0335                     break;
0336                 }
0337 
0338                 auto jt = dataContent.find(pos->second.parent);
0339                 if (jt != dataContent.end()) {
0340                     addpath.push(pos);
0341                     pos = &(*jt);
0342                 } else
0343                     break;
0344             }
0345             while (!addpath.empty()) {
0346                 auto pos = addpath.pop();
0347                 ROOTFilter::Directory dir;
0348                 dir.name = QString::fromStdString(pos->second.name);
0349                 currentdir->children << dir;
0350                 currentdir = &currentdir->children.last();
0351                 filledDirs[pos] = currentdir;
0352             }
0353             for (auto hist : path.second.content) {
0354                 auto name = ((*currentROOTData).*nameFunc)(hist);
0355                 if (!name.empty())
0356                     currentdir->content << qMakePair(QString::fromStdString(name), hist);
0357             }
0358         }
0359     }
0360 
0361     return dirs;
0362 }
0363 
0364 ROOTFilter::Directory ROOTFilterPrivate::listHistograms(const QString& fileName) {
0365     if (setFile(fileName))
0366         return listContent(currentROOTData->listHistograms(), &ROOTData::histogramName);
0367     else
0368         return ROOTFilter::Directory{};
0369 }
0370 
0371 ROOTFilter::Directory ROOTFilterPrivate::listTrees(const QString& fileName) {
0372     if (setFile(fileName))
0373         return listContent(currentROOTData->listTrees(), &ROOTData::treeName);
0374     else
0375         return ROOTFilter::Directory{};
0376 }
0377 
0378 QVector<QStringList> ROOTFilterPrivate::listLeaves(const QString& fileName, quint64 pos) {
0379     QVector<QStringList> leafList;
0380 
0381     if (setFile(fileName)) {
0382         for (const auto& leaf : currentROOTData->listLeaves(pos)) {
0383             leafList << QStringList(QString::fromStdString(leaf.branch));
0384             if (leaf.branch != leaf.leaf)
0385                 leafList.last() << QString::fromStdString(leaf.leaf);
0386             if (leaf.elements > 1)
0387                 leafList.last() << QStringLiteral("[%1]").arg(leaf.elements);
0388         }
0389     }
0390 
0391     return leafList;
0392 }
0393 
0394 QVector<QStringList> ROOTFilterPrivate::previewCurrentObject(const QString& fileName, int first, int last) {
0395     DEBUG(Q_FUNC_INFO);
0396 
0397     long int pos = 0;
0398     auto type = currentObjectPosition(fileName, pos);
0399     if (pos == 0)
0400         return {1, QStringList()};
0401 
0402     if (type == FileType::Hist) {
0403         auto bins = readHistogram(pos);
0404         const int nbins = static_cast<int>(bins.size());
0405 
0406         last = std::min(nbins - 1, last);
0407 
0408         QVector<QStringList> preview(std::max(last - first + 2, 1));
0409         DEBUG(" reading " << preview.size() - 1 << " lines");
0410 
0411         // set headers
0412         for (const auto& l : columns) {
0413             preview.last() << l.last();
0414         }
0415 
0416         // read data
0417         for (const auto& l : columns) {
0418             if (l.first() == QStringLiteral("center")) {
0419                 for (int i = first; i <= last; ++i)
0420                     preview[i - first] << QString::number((i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge)
0421                                                               : i == 0             ? bins.front().lowedge // -infinity
0422                                                                                    : -bins.front().lowedge); // +infinity
0423             } else if (l.first() == QStringLiteral("low")) {
0424                 for (int i = first; i <= last; ++i)
0425                     preview[i - first] << QString::number(bins[i].lowedge);
0426             } else if (l.first() == QStringLiteral("content")) {
0427                 for (int i = first; i <= last; ++i)
0428                     preview[i - first] << QString::number(bins[i].content);
0429             } else if (l.first() == QStringLiteral("error")) {
0430                 for (int i = first; i <= last; ++i)
0431                     preview[i - first] << QString::number(std::sqrt(bins[i].sumw2));
0432             }
0433         }
0434 
0435         return preview;
0436     } else if (type == FileType::Tree) {
0437         last = std::min(last, currentROOTData->treeEntries(pos) - 1);
0438 
0439         QVector<QStringList> preview(std::max(last - first + 2, 1));
0440         DEBUG(" reading " << preview.size() - 1 << " lines");
0441 
0442         // read data leaf by leaf and set headers
0443         for (const auto& l : columns) {
0444             unsigned int element = 0;
0445             QString lastelement = l.back(), leaf = l.front();
0446             bool isArray = false;
0447             if (lastelement.at(0) == QLatin1Char('[') && lastelement.at(lastelement.size() - 1) == QLatin1Char(']')) {
0448                 element = lastelement.mid(1, lastelement.length() - 2).toUInt(&isArray);
0449                 if (!isArray)
0450                     element = 0;
0451                 if (l.count() > 2)
0452                     leaf = l.at(1);
0453             } else if (l.count() > 1)
0454                 leaf = l.at(1);
0455 
0456             auto data = readTree(pos, l.first(), leaf, (int)element, last);
0457             for (int i = first; i <= last; ++i)
0458                 preview[i - first] << QString::number(data[i]);
0459             if (!isArray || l.count() == 2)
0460                 preview.last() << l.join(isArray ? QString() : QLatin1String(":"));
0461             else
0462                 preview.last() << l.first() + QLatin1Char(':') + l.at(1) + l.back();
0463         }
0464 
0465         return preview;
0466     }
0467 
0468     return {1, QStringList()};
0469 }
0470 
0471 int ROOTFilterPrivate::rowsInCurrentObject(const QString& fileName) {
0472     long int pos = 0;
0473     auto type = currentObjectPosition(fileName, pos);
0474     if (pos == 0)
0475         return 0;
0476 
0477     switch (type) {
0478     case FileType::Hist:
0479         return currentROOTData->histogramBins(pos);
0480     case FileType::Tree:
0481         return currentROOTData->treeEntries(pos);
0482     case FileType::Invalid:
0483     default:
0484         return 0;
0485     }
0486 }
0487 
0488 bool ROOTFilterPrivate::setFile(const QString& fileName) {
0489     QFileInfo file(fileName);
0490     if (!file.exists()) {
0491         currentObject.clear();
0492         columns.clear();
0493         currentROOTData.reset();
0494         return false;
0495     }
0496 
0497     QDateTime modified = file.lastModified();
0498     qint64 size = file.size();
0499     if (!currentROOTData || fileName != currentFile.name || modified != currentFile.modified || size != currentFile.size) {
0500         currentFile.name = fileName;
0501         currentFile.modified = modified;
0502         currentFile.size = size;
0503         currentROOTData.reset(new ROOTData(fileName.toStdString()));
0504     }
0505     return true;
0506 }
0507 
0508 std::vector<ROOTData::BinPars> ROOTFilterPrivate::readHistogram(quint64 pos) {
0509     return currentROOTData->readHistogram(pos);
0510 }
0511 
0512 std::vector<double> ROOTFilterPrivate::readTree(quint64 pos, const QString& branchName, const QString& leafName, int element, int last) {
0513     // QDEBUG("branch/leaf name =" << branchName << " " << leafName << ", element/last =" << element << " " << last)
0514     return currentROOTData->listEntries<double>(pos, branchName.toStdString(), leafName.toStdString(), element, last + 1);
0515 }
0516 
0517 /******************** ROOTData implementation ************************/
0518 
0519 namespace ROOTDataHelpers {
0520 
0521 /// Read value from stream
0522 template<class T>
0523 T read(std::ifstream& is) {
0524     union {
0525         T val;
0526         char buf[sizeof(T)];
0527     } r;
0528     for (size_t i = 0; i < sizeof(T); ++i)
0529         is.get(r.buf[sizeof(T) - i - 1]);
0530 
0531     return r.val;
0532 }
0533 
0534 /// Read value from buffer
0535 template<class T>
0536 T read(char*& s) {
0537     union {
0538         T val;
0539         char buf[sizeof(T)];
0540     } r;
0541     for (size_t i = 0; i < sizeof(T); ++i)
0542         r.buf[sizeof(T) - i - 1] = *(s++);
0543 
0544     return r.val;
0545 }
0546 
0547 /// Read value from buffer and cast to U
0548 template<class T, class U>
0549 U readcast(char*& s) {
0550     return static_cast<U>(read<T>(s));
0551 }
0552 
0553 /// Get version of ROOT object, obtain number of bytes in object
0554 short Version(char*& buffer, size_t& count) {
0555     // root/io/io/src/TBufferFile.cxx -> ReadVersion
0556     count = read<unsigned int>(buffer);
0557     short version = (count & 0x40000000) ? read<short>(buffer) : read<short>(buffer -= 4);
0558     count = (count & 0x40000000) ? (count & ~0x40000000) - 2 : 2;
0559     return version;
0560 }
0561 
0562 /// Get version of ROOT object
0563 short Version(char*& buffer) {
0564     size_t c;
0565     return Version(buffer, c);
0566 }
0567 
0568 /// Skip ROOT object
0569 void Skip(char*& buffer, size_t n) {
0570     for (size_t i = 0; i < n; ++i) {
0571         size_t count;
0572         Version(buffer, count);
0573         buffer += count;
0574     }
0575 }
0576 
0577 /// Skip TObject header
0578 void SkipObject(char*& buffer) {
0579     Version(buffer);
0580     buffer += 8;
0581 }
0582 
0583 /// Get TString
0584 std::string String(char*& buffer) {
0585     // root/io/io/src/TBufferFile.cxx -> ReadTString
0586     size_t s = *(buffer++);
0587     if (s == 0)
0588         return {};
0589     else {
0590         if (s == 0xFF)
0591             s = read<int>(buffer);
0592         buffer += s;
0593         return {buffer - s, buffer};
0594     }
0595 }
0596 
0597 /// Get the header of an object in TObjArray
0598 std::string readObject(char*& buf, char* const buf0, std::map<size_t, std::string>& tags) {
0599     // root/io/io/src/TBufferFile.cxx -> ReadObjectAny
0600     std::string clname;
0601     unsigned int tag = read<unsigned int>(buf);
0602     if (tag & 0x40000000) {
0603         tag = read<unsigned int>(buf);
0604         if (tag == 0xFFFFFFFF) {
0605             tags[buf - buf0 - 2] = clname = buf;
0606             buf += clname.size() + 1;
0607         } else {
0608             clname = tags[tag & ~0x80000000];
0609         }
0610     }
0611 
0612     return clname;
0613 }
0614 
0615 }
0616 
0617 using namespace ROOTDataHelpers;
0618 
0619 ROOTData::ROOTData(const std::string& filename)
0620     : filename(filename) {
0621     // The file structure is described in root/io/io/src/TFile.cxx
0622     std::ifstream is(filename, std::ifstream::binary);
0623     std::string root(4, 0);
0624     is.read(const_cast<char*>(root.data()), 4);
0625     if (root != "root")
0626         return;
0627 
0628     int fileVersion = read<int>(is);
0629     long int pos = read<int>(is);
0630     histdirs.emplace(pos, Directory{});
0631     treedirs.emplace(pos, Directory{});
0632     long int endpos = fileVersion < 1000000 ? read<int>(is) : read<long int>(is);
0633 
0634     is.seekg(33);
0635     int compression = read<int>(is);
0636     compression = compression > 0 ? compression : 0;
0637 
0638     while (is.good() && pos < endpos) {
0639         is.seekg(pos);
0640         int lcdata = read<int>(is);
0641         if (lcdata == 0) {
0642             break;
0643         }
0644         if (lcdata < 0) {
0645             pos -= lcdata;
0646             continue;
0647         }
0648         short version = read<short>(is);
0649         size_t ldata = read<unsigned int>(is);
0650         is.seekg(4, is.cur); // skip the date
0651         size_t lkey = read<unsigned short int>(is);
0652         short cycle = read<short>(is);
0653         long int pseek;
0654         if (version > 1000) {
0655             is.seekg(8, is.cur);
0656             pseek = read<long int>(is);
0657         } else {
0658             is.seekg(4, is.cur);
0659             pseek = read<int>(is);
0660         }
0661         std::string cname(read<unsigned char>(is), 0);
0662         is.read(&cname[0], cname.size());
0663         std::string name(read<unsigned char>(is), 0);
0664         is.read(&name[0], name.size());
0665         std::string title(read<unsigned char>(is), 0);
0666         is.read(&title[0], title.size());
0667 
0668         ContentType type = ContentType::Invalid;
0669         if (cname.size() == 4 && cname.substr(0, 3) == "TH1") {
0670             type = histType(cname[3]);
0671         } else if (cname == "TTree")
0672             type = ContentType::Tree;
0673         else if (cname.substr(0, 7) == "TNtuple")
0674             type = ContentType::NTuple;
0675         else if (cname == "TBasket")
0676             type = ContentType::Basket;
0677         else if (cname == "TList" && name == "StreamerInfo")
0678             type = ContentType::Streamer;
0679         else if (cname == "TDirectory") {
0680             auto it = histdirs.find(pseek);
0681             if (it == histdirs.end())
0682                 it = histdirs.begin();
0683             histdirs.emplace(pos, Directory{name, it->first});
0684             it = treedirs.find(pseek);
0685             if (it == treedirs.end())
0686                 it = treedirs.begin();
0687             treedirs.emplace(pos, Directory{name, it->first});
0688         }
0689 
0690         if (type != ContentType::Invalid) {
0691             if (type == ContentType::Basket)
0692                 is.seekg(19, std::ifstream::cur); // TODO read info instead?
0693             KeyBuffer buffer;
0694             buffer.type = ContentType::Invalid;
0695             // see root/io/io/src/TKey.cxx for reference
0696             int complib = 0;
0697             if (compression) {
0698                 // Default: compression level
0699                 // ZLIB: 100 + compression level
0700                 // LZ4:  400 + compression level
0701                 // do not rely on this, but read the header
0702                 std::string lib(2, 0);
0703                 is.read(&lib[0], 2);
0704                 complib = lib == "ZL" ? 1 : lib == "XZ" ? 2 : lib == "CS" ? 3 : lib == "L4" ? 4 : 0;
0705             }
0706             if (complib > 0) {
0707 #ifdef HAVE_ZIP
0708                 // see root/core/zip/src/RZip.cxx -> R__unzip
0709                 const int method = is.get();
0710                 size_t chcdata = is.get();
0711                 chcdata |= (is.get() << 8);
0712                 chcdata |= (is.get() << 16);
0713                 size_t chdata = is.get();
0714                 chdata |= (is.get() << 8);
0715                 chdata |= (is.get() << 16);
0716 
0717                 if (chcdata == lcdata - lkey - 9 && chdata == ldata) {
0718                     if (complib == 1 && method == Z_DEFLATED) {
0719                         buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::CompressionType::zlib, pos + lkey + 9, chcdata, chdata, 0};
0720                     } else if (complib == 4 && method == LZ4_versionNumber() / 10000) {
0721                         buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::CompressionType::lz4, pos + lkey + 9 + 8, chcdata - 8, chdata, 0};
0722                     }
0723                 }
0724 #endif
0725             } else {
0726                 buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::CompressionType::none, pos + lkey, ldata, ldata, 0};
0727             }
0728             switch (buffer.type) {
0729             case ContentType::Basket:
0730                 basketkeys.emplace(pos, buffer);
0731                 break;
0732             case ContentType::Tree:
0733             case ContentType::NTuple: {
0734                 auto it = treedirs.find(pseek);
0735                 if (it == treedirs.end())
0736                     it = treedirs.begin();
0737                 bool keyreplaced = false;
0738                 for (auto& tpos : it->second.content) {
0739                     auto jt = treekeys.find(tpos);
0740                     if (jt != treekeys.end() && jt->second.name == buffer.name && jt->second.cycle < buffer.cycle) {
0741                         // override key with lower cylce number
0742                         tpos = pos;
0743                         treekeys.erase(jt);
0744                         keyreplaced = true;
0745                         break;
0746                     }
0747                 }
0748                 if (!keyreplaced)
0749                     it->second.content.push_back(pos);
0750                 treekeys.emplace(pos, buffer);
0751                 break;
0752             }
0753             case ContentType::Streamer:
0754                 readStreamerInfo(buffer);
0755                 break;
0756             case ContentType::Double:
0757             case ContentType::Float:
0758             case ContentType::Int:
0759             case ContentType::Short:
0760             case ContentType::Byte: {
0761                 auto it = histdirs.find(pseek);
0762                 if (it == histdirs.end())
0763                     it = histdirs.begin();
0764                 it->second.content.push_back(pos);
0765                 histkeys.emplace(pos, buffer);
0766                 break;
0767             }
0768             case ContentType::Invalid:
0769             case ContentType::Long:
0770             case ContentType::Bool:
0771             case ContentType::CString:
0772                 break;
0773             }
0774         }
0775         pos += lcdata;
0776     }
0777 
0778     // Create default object structures if no StreamerInfo was found.
0779     // Obtained by running the following in ROOT with a file passed as an argument:
0780     //
0781     // _file0->GetStreamerInfoList()->Print()
0782     //
0783     // auto l = (TStreamerInfo*)_file0->GetStreamerInfoList()->At(ENTRYNUMBER);
0784     // l->Print();
0785     // for (int i = 0; i < l->GetNelement(); ++i) {
0786     //     auto e = l->GetElement(i);
0787     //     e->Print();
0788     //     cout << e->GetFullName() << " " << e->GetTypeName() << " " << e->GetSize() << endl;
0789     // }
0790 
0791     static const StreamerInfo dummyobject{"Object", 0, std::string(), false, false};
0792     if (!treekeys.empty()) {
0793         if (!streamerInfo.count("TTree")) {
0794             streamerInfo["TTree"] = {dummyobject,
0795                                      dummyobject,
0796                                      dummyobject,
0797                                      dummyobject,
0798                                      StreamerInfo{"fEntries", 8, std::string(), false, false},
0799                                      StreamerInfo{std::string(), 5 * 8 + 4 * 4, std::string(), false, false},
0800                                      StreamerInfo{"fNClusterRange", 4, std::string(), true, false},
0801                                      StreamerInfo{std::string(), 6 * 8, std::string(), false, false},
0802                                      StreamerInfo{"fNClusterRangeEnd", 8, "fNClusterRange", false, true},
0803                                      StreamerInfo{"fNClusterSize", 8, "fNClusterRange", false, true},
0804                                      StreamerInfo{"fBranches", 0, std::string(), false, false}};
0805         }
0806         if (!streamerInfo.count("TBranch")) {
0807             streamerInfo["TBranch"] = {StreamerInfo{"TNamed", 0, std::string(), false, false},
0808                                        dummyobject,
0809                                        StreamerInfo{std::string(), 3 * 4, std::string(), false, false},
0810                                        StreamerInfo{"fWriteBasket", 4, std::string(), false, false},
0811                                        StreamerInfo{std::string(), 8 + 4, std::string(), false, false},
0812                                        StreamerInfo{"fMaxBaskets", 4, std::string(), true, false},
0813                                        StreamerInfo{std::string(), 4 + 4 * 8, std::string(), false, false},
0814                                        StreamerInfo{"fBranches", 0, std::string(), false, false},
0815                                        StreamerInfo{"fLeaves", 0, std::string(), false, false},
0816                                        StreamerInfo{"fBaskets", 0, std::string(), false, false},
0817                                        StreamerInfo{"fBasketBytes", 4, "fMaxBaskets", false, true},
0818                                        StreamerInfo{"fBasketEntry", 8, "fMaxBaskets", false, true},
0819                                        StreamerInfo{"fBasketSeek", 8, "fMaxBaskets", false, true}};
0820         }
0821     }
0822     if (!histkeys.empty()) {
0823         if (!streamerInfo.count("TH1")) {
0824             streamerInfo["TH1"] = {dummyobject,
0825                                    dummyobject,
0826                                    dummyobject,
0827                                    dummyobject,
0828                                    StreamerInfo{"fNcells", 4, std::string(), false, false},
0829                                    StreamerInfo{"fXaxis", 0, std::string(), false, false},
0830                                    StreamerInfo{"fYaxis", 0, std::string(), false, false},
0831                                    StreamerInfo{"fZaxis", 0, std::string(), false, false},
0832                                    StreamerInfo{std::string(), 2 * 2 + 8 * 8, std::string(), false, false},
0833                                    dummyobject,
0834                                    StreamerInfo{"fSumw2", 0, std::string(), false, false}};
0835         }
0836         if (!streamerInfo.count("TAxis")) {
0837             streamerInfo["TAxis"] = {dummyobject,
0838                                      dummyobject,
0839                                      StreamerInfo{"fNbins", 4, std::string(), false, false},
0840                                      StreamerInfo{"fXmin", 8, std::string(), false, false},
0841                                      StreamerInfo{"fXmax", 8, std::string(), false, false},
0842                                      StreamerInfo{"fXbins", 0, std::string(), false, false}};
0843         }
0844     }
0845 
0846     for (auto& tree : treekeys)
0847         readNEntries(tree.second);
0848     for (auto& hist : histkeys)
0849         readNBins(hist.second);
0850 }
0851 
0852 void ROOTData::readNBins(ROOTData::KeyBuffer& kbuffer) {
0853     std::string buffer = data(kbuffer);
0854     if (!buffer.empty()) {
0855         char* buf = &buffer[0];
0856         std::map<std::string, size_t> counts;
0857         Version(buf); // TH1(D/F/I/S/C)
0858         Version(buf); // TH1
0859         advanceTo(buf, streamerInfo.find("TH1")->second, std::string(), "fNcells", counts);
0860         kbuffer.nrows = read<int>(buf); // fNcells
0861     }
0862 }
0863 
0864 std::string ROOTData::histogramName(long int pos) {
0865     auto it = histkeys.find(pos);
0866     if (it != histkeys.end())
0867         return it->second.name + ';' + std::to_string(it->second.cycle);
0868     return {};
0869 }
0870 
0871 int ROOTData::histogramBins(long int pos) {
0872     auto it = histkeys.find(pos);
0873     if (it != histkeys.end())
0874         return it->second.nrows;
0875     return 0;
0876 }
0877 
0878 std::vector<ROOTData::BinPars> ROOTData::readHistogram(long int pos) {
0879     auto it = histkeys.find(pos);
0880     if (it == histkeys.end())
0881         return {};
0882 
0883     std::string buffer = data(it->second);
0884     if (!buffer.empty()) {
0885         char* buf = &buffer[0];
0886         std::map<std::string, size_t> counts;
0887         auto& streamerTH1 = streamerInfo.find("TH1")->second;
0888         auto& streamerTAxis = streamerInfo.find("TAxis")->second;
0889 
0890         size_t count;
0891         Version(buf); // TH1(D/F/I/S/C)
0892         Version(buf, count); // TH1
0893         char* const dbuf = buf + count;
0894         advanceTo(buf, streamerTH1, std::string(), "fNcells", counts);
0895 
0896         std::vector<BinPars> r(read<int>(buf)); // fNcells
0897         if (r.size() < 3)
0898             return {};
0899 
0900         r.front().lowedge = -std::numeric_limits<double>::infinity();
0901 
0902         advanceTo(buf, streamerTH1, "fNcells", "fXaxis", counts);
0903         // x-Axis
0904         Version(buf, count); // TAxis
0905         char* const nbuf = buf + count;
0906         advanceTo(buf, streamerTAxis, std::string(), "fNbins", counts);
0907         const int nbins = read<int>(buf);
0908         advanceTo(buf, streamerTAxis, "fNbins", "fXmin", counts);
0909         const double xmin = read<double>(buf);
0910         advanceTo(buf, streamerTAxis, "fXmin", "fXmax", counts);
0911         const double xmax = read<double>(buf);
0912         advanceTo(buf, streamerTAxis, "fXmax", "fXbins", counts);
0913         const size_t nborders = read<int>(buf); // TArrayD
0914         // root/core/cont/src/TArrayD.cxx -> Streamer
0915         if (nborders == r.size() - 1) {
0916             for (size_t i = 0; i < nborders; ++i) {
0917                 r[i + 1].lowedge = read<double>(buf);
0918             }
0919         } else {
0920             // UNUSED: buf += sizeof(double) * nbins;
0921             const double scale = (xmax - xmin) / static_cast<double>(nbins);
0922             for (size_t i = 0; i < r.size() - 1; ++i) {
0923                 r[i + 1].lowedge = static_cast<double>(i) * scale + xmin;
0924             }
0925         }
0926         buf = nbuf; // go beyond x-Axis
0927 
0928         advanceTo(buf, streamerTH1, "fXaxis", "fSumw2", counts);
0929         if (static_cast<size_t>(read<int>(buf)) == r.size()) { // TArrayD
0930             for (auto& b : r)
0931                 b.sumw2 = read<double>(buf); // always double
0932         }
0933         buf = dbuf; // skip to contents of TH1(D/F/I/S/C)
0934 
0935         if (static_cast<size_t>(read<int>(buf)) == r.size()) {
0936             auto readf = readType<double>(it->second.type);
0937             for (auto& b : r)
0938                 b.content = readf(buf);
0939         }
0940 
0941         return r;
0942     } else
0943         return {};
0944 }
0945 
0946 void ROOTData::readNEntries(ROOTData::KeyBuffer& kbuffer) {
0947     std::string buffer = data(kbuffer);
0948     if (!buffer.empty()) {
0949         char* buf = &buffer[0];
0950         std::map<std::string, size_t> counts;
0951         if (kbuffer.type == ContentType::NTuple)
0952             Version(buf); // TNtuple(D)
0953         Version(buf); // TTree
0954         advanceTo(buf, streamerInfo.find("TTree")->second, std::string(), "fEntries", counts);
0955         kbuffer.nrows = read<long int>(buf); // fEntries
0956     }
0957 }
0958 
0959 std::string ROOTData::treeName(long int pos) {
0960     auto it = treekeys.find(pos);
0961     if (it != treekeys.end())
0962         return it->second.name;
0963     return {};
0964 }
0965 
0966 int ROOTData::treeEntries(long int pos) {
0967     auto it = treekeys.find(pos);
0968     if (it != treekeys.end())
0969         return it->second.nrows;
0970     else
0971         return 0;
0972 }
0973 
0974 std::vector<ROOTData::LeafInfo> ROOTData::listLeaves(long int pos) const {
0975     std::vector<LeafInfo> leaves;
0976 
0977     auto it = treekeys.find(pos);
0978     if (it == treekeys.end())
0979         return leaves;
0980 
0981     std::ifstream is(filename, std::ifstream::binary);
0982     std::string datastring = data(it->second, is);
0983     if (datastring.empty())
0984         return leaves;
0985 
0986     char* buf = &datastring[0];
0987     char* const buf0 = buf - it->second.keylength;
0988     std::map<std::string, size_t> counts;
0989     auto& streamerTBranch = streamerInfo.find("TBranch")->second;
0990 
0991     if (it->second.type == ContentType::NTuple)
0992         Version(buf); // TNtuple(D)
0993     Version(buf); // TTree
0994     advanceTo(buf, streamerInfo.find("TTree")->second, std::string(), "fBranches", counts);
0995 
0996     // read the list of branches
0997     Version(buf); // TObjArray
0998     SkipObject(buf);
0999     String(buf);
1000     const size_t nbranches = read<int>(buf);
1001     const size_t lowb = read<int>(buf);
1002     std::map<size_t, std::string> tags;
1003     for (size_t i = 0; i < nbranches; ++i) {
1004         std::string clname = readObject(buf, buf0, tags);
1005         size_t count;
1006         Version(buf, count); // TBranch or TBranchElement
1007         char* const nbuf = buf + count;
1008         if (i >= lowb) {
1009             if (clname == "TBranchElement") {
1010                 Version(buf); // TBranch
1011             }
1012             advanceTo(buf, streamerTBranch, std::string(), "TNamed", counts);
1013             Version(buf); // TNamed
1014             SkipObject(buf);
1015             const std::string branch = String(buf);
1016             String(buf);
1017             // TODO add reading of nested branches (fBranches)
1018             advanceTo(buf, streamerTBranch, "TNamed", "fLeaves", counts);
1019 
1020             // fLeaves
1021             Version(buf); // TObjArray
1022             SkipObject(buf);
1023             String(buf);
1024             const size_t nleaves = read<int>(buf);
1025             const size_t lowb = read<int>(buf);
1026             for (size_t i = 0; i < nleaves; ++i) {
1027                 std::string clname = readObject(buf, buf0, tags);
1028                 Version(buf, count); // TLeaf(D/F/B/S/I/L/C/O)
1029                 char* nbuf = buf + count;
1030                 if (i >= lowb && clname.size() == 6 && clname.compare(0, 5, "TLeaf") == 0) {
1031                     Version(buf); // TLeaf
1032                     Version(buf); // TNamed
1033                     SkipObject(buf);
1034                     const std::string leafname = String(buf);
1035                     String(buf); // title
1036                     size_t elements = read<int>(buf);
1037                     int bytes = read<int>(buf);
1038                     if ((static_cast<int>(leafType(clname.back())) & 0xF) != bytes)
1039                         DEBUG("ROOTData: type " << clname.back() << " does not match its size!")
1040                     buf += 5;
1041                     leaves.emplace_back(LeafInfo{branch, leafname, leafType(clname.back()), !read<char>(buf), elements});
1042                 }
1043 
1044                 buf = nbuf;
1045             }
1046         }
1047 
1048         buf = nbuf;
1049     }
1050     return leaves;
1051 }
1052 
1053 template<class T>
1054 std::vector<T>
1055 ROOTData::listEntries(long int pos, const std::string& branchname, const std::string& leafname, const size_t element, const size_t nentries) const {
1056     std::vector<T> entries;
1057 
1058     auto it = treekeys.find(pos);
1059     if (it == treekeys.end())
1060         return entries;
1061 
1062     std::ifstream is(filename, std::ifstream::binary);
1063     std::string datastring = data(it->second, is);
1064     if (datastring.empty())
1065         return entries;
1066 
1067     char* buf = &datastring[0];
1068     char* const buf0 = buf - it->second.keylength;
1069     std::map<std::string, size_t> counts;
1070     auto& streamerTTree = streamerInfo.find("TTree")->second;
1071     auto& streamerTBranch = streamerInfo.find("TBranch")->second;
1072 
1073     if (it->second.type == ContentType::NTuple)
1074         Version(buf); // TNtuple(D)
1075     Version(buf); // TTree
1076     advanceTo(buf, streamerTTree, std::string(), "fEntries", counts);
1077     entries.reserve(std::min(static_cast<size_t>(read<long int>(buf)), nentries)); // reserve space (maximum for number of entries)
1078     advanceTo(buf, streamerTTree, "fEntries", "fBranches", counts);
1079 
1080     // read the list of branches
1081     Version(buf); // TObjArray
1082     SkipObject(buf);
1083     String(buf);
1084     const size_t nbranches = read<int>(buf);
1085     const size_t lowb = read<int>(buf);
1086     std::map<size_t, std::string> tags;
1087     for (size_t i = 0; i < nbranches; ++i) {
1088         std::string clname = readObject(buf, buf0, tags);
1089         size_t count;
1090         Version(buf, count); // TBranch or TBranchElement
1091         char* const nbuf = buf + count;
1092         if (i >= lowb) {
1093             if (clname == "TBranchElement") {
1094                 Version(buf);
1095             }
1096             Version(buf); // TNamed
1097             SkipObject(buf);
1098             const std::string currentbranch = String(buf);
1099             String(buf);
1100 
1101             advanceTo(buf, streamerTBranch, "TNamed", "fWriteBasket", counts);
1102             int fWriteBasket = read<int>(buf);
1103             // TODO add reading of nested branches (fBranches)
1104             advanceTo(buf, streamerTBranch, "fWriteBasket", "fLeaves", counts);
1105 
1106             // fLeaves
1107             Version(buf); // TObjArray
1108             SkipObject(buf);
1109             String(buf);
1110             const size_t nleaves = read<int>(buf);
1111             const size_t lowb = read<int>(buf);
1112             int leafoffset = 0, leafcount = 0, leafcontent = 0, leafsize = 0;
1113             bool leafsign = false;
1114             ContentType leaftype = ContentType::Invalid;
1115             for (size_t i = 0; i < nleaves; ++i) {
1116                 std::string clname = readObject(buf, buf0, tags);
1117                 Version(buf, count); // TLeaf(D/F/L/I/S/B/O/C/Element)
1118                 char* nbuf = buf + count;
1119                 if (currentbranch == branchname) {
1120                     if (i >= lowb && clname.size() >= 5 && clname.compare(0, 5, "TLeaf") == 0) {
1121                         Version(buf); // TLeaf
1122                         Version(buf); // TNamed
1123                         SkipObject(buf);
1124                         const bool istheleaf = (clname.size() == 6 && leafname == String(buf));
1125                         String(buf);
1126                         const int len = read<int>(buf);
1127                         const int size = read<int>(buf);
1128                         if (istheleaf) {
1129                             leafoffset = leafcount;
1130                             leafsize = size;
1131                             leaftype = leafType(clname.back());
1132                         }
1133                         leafcount += len * size;
1134                         if (istheleaf) {
1135                             leafcontent = leafcount - leafoffset;
1136                             buf += 1;
1137                             leafsign = !read<bool>(buf);
1138                         }
1139                     }
1140                 }
1141 
1142                 buf = nbuf;
1143             }
1144             if (leafcontent == 0) {
1145                 buf = nbuf;
1146                 continue;
1147             }
1148 
1149             if (static_cast<int>(element) * leafsize >= leafcontent) {
1150                 DEBUG("ROOTData: " << leafname.c_str() << " only contains " << leafcontent / leafsize << " elements.");
1151                 break;
1152             }
1153 
1154             advanceTo(buf, streamerTBranch, "fLeaves", "fBaskets", counts);
1155             // fBaskets (probably empty)
1156             Version(buf, count); // TObjArray
1157             char* const basketsbuf = buf += count + 1; // TODO there is one byte to be skipped in fBaskets, why is that?
1158 
1159             advanceTo(buf, streamerTBranch, "fBaskets", "fBasketEntry", counts);
1160             for (int i = 0; i <= fWriteBasket; ++i) {
1161                 if (static_cast<size_t>(read<long int>(buf)) > nentries) {
1162                     fWriteBasket = i;
1163                     break;
1164                 }
1165             }
1166             // rewind to the end of fBaskets and look for the fBasketSeek array
1167             advanceTo(buf = basketsbuf, streamerTBranch, "fBaskets", "fBasketSeek", counts);
1168             auto readf = readType<T>(leaftype, leafsign);
1169             for (int i = 0; i < fWriteBasket; ++i) {
1170                 long int pos = read<long int>(buf);
1171                 auto it = basketkeys.find(pos);
1172                 if (it != basketkeys.end()) {
1173                     std::string basketbuffer = data(it->second);
1174                     if (!basketbuffer.empty()) {
1175                         char* bbuf = &basketbuffer[0];
1176                         char* const bufend = bbuf + basketbuffer.size();
1177                         while (bbuf + leafcount <= bufend && entries.size() < nentries) {
1178                             bbuf += leafoffset + leafsize * element;
1179                             entries.emplace_back(readf(bbuf));
1180                             bbuf += leafcount - leafsize * (element + 1) - leafoffset;
1181                         }
1182                     }
1183                 } else {
1184                     DEBUG("ROOTData: fBasketSeek(" << i << "): " << pos << " (not available)")
1185                 }
1186             }
1187         }
1188 
1189         buf = nbuf;
1190     }
1191 
1192     return entries;
1193 }
1194 
1195 ROOTData::ContentType ROOTData::histType(const char type) {
1196     switch (type) {
1197     case 'D':
1198         return ContentType::Double;
1199     case 'F':
1200         return ContentType::Float;
1201     case 'I':
1202         return ContentType::Int;
1203     case 'S':
1204         return ContentType::Short;
1205     case 'C':
1206         return ContentType::Byte;
1207     default:
1208         return ContentType::Invalid;
1209     }
1210 }
1211 
1212 ROOTData::ContentType ROOTData::leafType(const char type) {
1213     switch (type) {
1214     case 'D':
1215         return ContentType::Double;
1216     case 'F':
1217         return ContentType::Float;
1218     case 'L':
1219         return ContentType::Long;
1220     case 'I':
1221         return ContentType::Int;
1222     case 'S':
1223         return ContentType::Short;
1224     case 'B':
1225         return ContentType::Byte;
1226     case 'O':
1227         return ContentType::Bool;
1228     case 'C':
1229         return ContentType::CString;
1230     default:
1231         return ContentType::Invalid;
1232     }
1233 }
1234 
1235 template<class T>
1236 T (*ROOTData::readType(ROOTData::ContentType type, bool sign) const)
1237 (char*&) {
1238     switch (type) {
1239     case ContentType::Double:
1240         return readcast<double, T>;
1241     case ContentType::Float:
1242         return readcast<float, T>;
1243     case ContentType::Long:
1244         return sign ? readcast<long, T> : readcast<unsigned long, T>;
1245     case ContentType::Int:
1246         return sign ? readcast<int, T> : readcast<unsigned int, T>;
1247     case ContentType::Short:
1248         return sign ? readcast<short, T> : readcast<unsigned short, T>;
1249     case ContentType::Byte:
1250         return sign ? readcast<char, T> : readcast<unsigned char, T>;
1251     case ContentType::Bool:
1252         return readcast<bool, T>;
1253     case ContentType::CString:
1254     case ContentType::Tree:
1255     case ContentType::NTuple:
1256     case ContentType::Basket:
1257     case ContentType::Streamer:
1258     case ContentType::Invalid:
1259         break;
1260     }
1261     return readcast<char, T>;
1262 }
1263 
1264 std::string ROOTData::data(const ROOTData::KeyBuffer& buffer) const {
1265     std::ifstream is(filename, std::ifstream::binary);
1266     return data(buffer, is);
1267 }
1268 
1269 std::string ROOTData::data(const ROOTData::KeyBuffer& buffer, std::ifstream& is) const {
1270     std::string data(buffer.count, 0);
1271     is.seekg(buffer.start);
1272     if (buffer.compression == KeyBuffer::CompressionType::none) {
1273         is.read(&data[0], buffer.count);
1274         return data;
1275 #ifdef HAVE_ZIP
1276     } else if (buffer.compression == KeyBuffer::CompressionType::zlib) {
1277         std::string cdata(buffer.compressed_count, 0);
1278         is.read(&cdata[0], buffer.compressed_count);
1279         uLongf luncomp = (uLongf)buffer.count;
1280         if (uncompress((Bytef*)data.data(), &luncomp, (Bytef*)cdata.data(), (uLong)cdata.size()) == Z_OK && data.size() == luncomp)
1281             return data;
1282     } else {
1283         std::string cdata(buffer.compressed_count, 0);
1284         is.read(&cdata[0], buffer.compressed_count);
1285         if (LZ4_decompress_safe(cdata.data(), const_cast<char*>(data.data()), (int)buffer.compressed_count, (int)buffer.count)
1286             == static_cast<int>(buffer.count))
1287             return data;
1288 #endif
1289     }
1290 
1291     return {};
1292 }
1293 
1294 void ROOTData::readStreamerInfo(const ROOTData::KeyBuffer& buffer) {
1295     std::ifstream is(filename, std::ifstream::binary);
1296     std::string datastring = data(buffer, is);
1297     if (!datastring.empty()) {
1298         char* buf = &datastring[0];
1299         char* const buf0 = buf - buffer.keylength;
1300         Version(buf);
1301         SkipObject(buf); // TCollection
1302         String(buf);
1303         const int nobj = read<int>(buf);
1304         std::map<size_t, std::string> tags;
1305         for (int i = 0; i < nobj; ++i) {
1306             std::string clname = readObject(buf, buf0, tags);
1307             size_t count;
1308             Version(buf, count);
1309             char* const nbuf = buf + count;
1310             if (clname == "TStreamerInfo") {
1311                 Version(buf);
1312                 SkipObject(buf);
1313                 std::vector<StreamerInfo>& sinfo = streamerInfo[String(buf)];
1314                 String(buf);
1315                 buf += 8; // skip check sum and version
1316 
1317                 clname = readObject(buf, buf0, tags);
1318                 Version(buf, count);
1319                 if (clname != "TObjArray") {
1320                     buf += count;
1321                     continue;
1322                 }
1323 
1324                 SkipObject(buf); // TObjArray
1325                 String(buf);
1326                 const int nobj = read<int>(buf);
1327                 const int lowb = read<int>(buf);
1328                 for (int i = 0; i < nobj; ++i) {
1329                     std::string clname = readObject(buf, buf0, tags);
1330                     Version(buf, count);
1331                     char* const nbuf = buf + count;
1332 
1333                     const bool isbasicpointer = clname == "TStreamerBasicPointer";
1334                     const bool ispointer = isbasicpointer || clname == "TStreamerObjectPointer";
1335                     if (i >= lowb) {
1336                         if (ispointer || clname == "TStreamerBase" || clname == "TStreamerBasicType" || clname == "TStreamerObject"
1337                             || clname == "TStreamerObjectAny" || clname == "TStreamerString" || clname == "TStreamerSTL") {
1338                             Version(buf); // TStreamerXXX
1339                             Version(buf); // TStreamerElement
1340                             SkipObject(buf);
1341                             const std::string name = String(buf);
1342                             const std::string title = String(buf);
1343                             int type = read<int>(buf);
1344                             size_t size = read<int>(buf);
1345 
1346                             if (clname.compare(0, 15, "TStreamerObject") == 0)
1347                                 size = 0;
1348                             std::string counter;
1349                             bool iscounter = false;
1350                             if (ispointer) {
1351                                 if (!title.empty() && title.front() == '[') {
1352                                     const size_t endref = title.find(']', 1);
1353                                     if (endref != title.npos) {
1354                                         counter = title.substr(1, endref - 1);
1355                                     }
1356                                 }
1357                                 if (isbasicpointer) {
1358                                     // see root/io/io/inc/TStreamerInfo.h -> TStreamerInfo::EReadWrite
1359                                     switch (type - 40) {
1360                                     case 1: // char
1361                                     case 11: // unsigned char
1362                                         size = 1;
1363                                         break;
1364                                     case 2: // short
1365                                     case 12: // unsigned short
1366                                     case 19: // float16
1367                                         size = 2;
1368                                         break;
1369                                     case 3: // int
1370                                     case 5: // float
1371                                     case 9: // double32
1372                                     case 13: // unsigned int
1373                                         size = 4;
1374                                         break;
1375                                     case 4: // long
1376                                     case 8: // double
1377                                     case 14: // unsigned long
1378                                     case 16: // long
1379                                     case 17: // unsigned long
1380                                         size = 8;
1381                                         break;
1382                                     }
1383                                 }
1384                             } else if (clname == "TStreamerBasicType") {
1385                                 iscounter = type == 6; // see root/io/io/inc/TStreamerInfo.h -> TStreamerInfo::EReadWrite
1386                             }
1387                             sinfo.emplace_back(StreamerInfo{name, size, counter, iscounter, ispointer});
1388                         }
1389                     }
1390                     buf = nbuf;
1391                 }
1392             } else
1393                 buf = nbuf;
1394             buf += 1; // trailing zero of TObjArray*
1395         }
1396     } else
1397         DEBUG("ROOTData: Inflation failed!")
1398 }
1399 
1400 bool ROOTData::advanceTo(char*& buf,
1401                          const std::vector<ROOTData::StreamerInfo>& objects,
1402                          const std::string& current,
1403                          const std::string& target,
1404                          std::map<std::string, size_t>& counts) {
1405     // The object structure can be retrieved from TFile::GetStreamerInfoList().
1406     // Every ROOT object contains a version number which may include the byte count
1407     // for the object. The latter is currently assumed to be present to skip unused
1408     // objects. No checks are performed. The corresponding ROOT code is quite nested
1409     // but the actual readout is straight forward.
1410     auto it = objects.begin();
1411     if (!current.empty()) {
1412         for (; it != objects.end(); ++it) {
1413             if (it->name == target) {
1414                 return false; // target lies before current buffer position
1415             } else if (it->name == current) {
1416                 ++it;
1417                 break;
1418             }
1419         }
1420     }
1421 
1422     for (; it != objects.end(); ++it) {
1423         if (it->name == target)
1424             return true;
1425 
1426         if (it->size == 0)
1427             Skip(buf, 1);
1428         else if (it->iscounter)
1429             counts[it->name] = read<int>(buf);
1430         else if (it->ispointer) {
1431             if (it->counter.empty())
1432                 buf += it->size + 1;
1433             else
1434                 buf += it->size * counts[it->counter] + 1;
1435         } else
1436             buf += it->size;
1437     }
1438 
1439     return false;
1440 }
1441 
1442 // needs to be after ROOTDataHelpers namespace declaration
1443 
1444 QString ROOTFilter::fileInfoString(const QString& fileName) {
1445     DEBUG("ROOTFilter::fileInfoString()");
1446     QString info;
1447 
1448     // The file structure is described in root/io/io/src/TFile.cxx
1449     std::ifstream is(fileName.toStdString(), std::ifstream::binary);
1450     std::string root(4, 0);
1451     is.read(const_cast<char*>(root.data()), 4);
1452     if (root != "root") {
1453         DEBUG(" Not a ROOT file. root = " << root);
1454         return i18n("Not a ROOT file");
1455     }
1456 
1457     int version = read<int>(is);
1458 
1459     info += i18n("File format version: %1", QString::number(version));
1460     info += QLatin1String("<br>");
1461 
1462     is.seekg(20);
1463     int freeBytes = read<int>(is);
1464     int freeRecords = read<int>(is);
1465     int namedBytes = read<int>(is);
1466     char pointerBytes = read<char>(is);
1467     info += i18n("FREE data record size: %1 bytes", QString::number(freeBytes));
1468     info += QLatin1String("<br>");
1469     info += i18n("Number of free data records: %1", QString::number(freeRecords));
1470     info += QLatin1String("<br>");
1471     info += i18n("TNamed size: %1 bytes", QString::number(namedBytes));
1472     info += QLatin1String("<br>");
1473     info += i18n("Size of file pointers: %1 bytes", QString::number(pointerBytes));
1474     info += QLatin1String("<br>");
1475 
1476     int compression = read<int>(is);
1477     compression = compression > 0 ? compression : 0;
1478     info += i18n("Compression level and algorithm: %1", QString::number(compression));
1479     info += QLatin1String("<br>");
1480 
1481     is.seekg(41);
1482     int infoBytes = read<int>(is);
1483     info += i18n("Size of TStreamerInfo record: %1 bytes", QString::number(infoBytes));
1484     info += QLatin1String("<br>");
1485 
1486     return info;
1487 }