refs #361, some own ini reader got lost in master, re-applied changes

This commit is contained in:
Klaus Basan
2015-05-06 02:46:42 +02:00
committed by Mathew Sutcliffe
parent 1c3bb8d463
commit 66075ca0d0
4 changed files with 244 additions and 171 deletions

View File

@@ -22,19 +22,13 @@ namespace BlackMisc
namespace FsCommon namespace FsCommon
{ {
/* CAircraftCfgEntries::CAircraftCfgEntries(const QString &fileName, qint32 index, const QString &title, const QString &atcType, const QString &atcModel, const QString &atcParkingCode, const QString &description) :
* Constructor m_index(index), m_fileName(fileName), m_title(title.trimmed()), m_atcType(atcType.trimmed()),
*/
CAircraftCfgEntries::CAircraftCfgEntries(const QString &filePath, qint32 index, const QString &title, const QString &atcType, const QString &atcModel, const QString &atcParkingCode, const QString &description) :
m_index(index), m_fileName(filePath), m_title(title.trimmed()), m_atcType(atcType.trimmed()),
m_atcModel(atcModel.trimmed()), m_atcParkingCode(atcParkingCode.trimmed()), m_description(description.trimmed()) m_atcModel(atcModel.trimmed()), m_atcParkingCode(atcParkingCode.trimmed()), m_description(description.trimmed())
{ {
// void // void
} }
/*
* String representation
*/
QString CAircraftCfgEntries::convertToQString(bool) const QString CAircraftCfgEntries::convertToQString(bool) const
{ {
QString s = "{%1, %2, %3, %4, %5, %6}"; QString s = "{%1, %2, %3, %4, %5, %6}";
@@ -43,9 +37,10 @@ namespace BlackMisc
return s; return s;
} }
/* CAircraftCfgEntries::CAircraftCfgEntries(const QString &fileName, int index) :
* Aircraft model m_index(index), m_fileName(fileName)
*/ { }
QString CAircraftCfgEntries::getFileDirectory() const QString CAircraftCfgEntries::getFileDirectory() const
{ {
if (this->m_fileName.isEmpty()) { return ""; } if (this->m_fileName.isEmpty()) { return ""; }
@@ -63,9 +58,31 @@ namespace BlackMisc
return d; return d;
} }
/* void CAircraftCfgEntries::setFileName(const QString &filePath)
* Convert {
*/ this->m_fileName = filePath.trimmed();
}
void CAircraftCfgEntries::setTitle(const QString &title)
{
this->m_title = title.trimmed();
}
void CAircraftCfgEntries::setAtcModel(const QString &atcModel)
{
this->m_atcModel = atcModel.trimmed();
}
void CAircraftCfgEntries::setAtcType(const QString &atcType)
{
this->m_atcType = atcType.trimmed();
}
void CAircraftCfgEntries::setAtcParkingCode(const QString &parkingCode)
{
this->m_atcParkingCode = parkingCode.trimmed();
}
CAircraftModel CAircraftCfgEntries::toAircraftModel() const CAircraftModel CAircraftCfgEntries::toAircraftModel() const
{ {
CAircraftModel model(this->getTitle(), CAircraftModel::TypeModelMapping); CAircraftModel model(this->getTitle(), CAircraftModel::TypeModelMapping);
@@ -82,9 +99,6 @@ namespace BlackMisc
return fn; return fn;
} }
/*
* Get particular column
*/
CVariant CAircraftCfgEntries::propertyByIndex(const BlackMisc::CPropertyIndex &index) const CVariant CAircraftCfgEntries::propertyByIndex(const BlackMisc::CPropertyIndex &index) const
{ {
if (index.isMyself()) { return this->toCVariant(); } if (index.isMyself()) { return this->toCVariant(); }
@@ -112,9 +126,6 @@ namespace BlackMisc
} }
} }
/*
* Set property as index
*/
void CAircraftCfgEntries::setPropertyByIndex(const CVariant &variant, const BlackMisc::CPropertyIndex &index) void CAircraftCfgEntries::setPropertyByIndex(const CVariant &variant, const BlackMisc::CPropertyIndex &index)
{ {
if (index.isMyself()) { this->convertFromCVariant(variant); return; } if (index.isMyself()) { this->convertFromCVariant(variant); return; }

View File

@@ -23,11 +23,9 @@ namespace BlackMisc
{ {
namespace FsCommon namespace FsCommon
{ {
/*! //! Set of aircraft.cfg entries representing an aircraft (FSX)
* Set of aircraft.cfg entries representing an aircraft (FSX) //! \remarks an entry in the aircraft.cfg is title, atc type, ...
* \remarks an entry in the aircraft.cfg is title, atc type, ... This class already bundles //! This class already bundles relevant entries, hence the class is named Entries (plural)
* relevant entries, hence the class is named Entries (plural)
*/
class BLACKMISC_EXPORT CAircraftCfgEntries: public BlackMisc::CValueObject<CAircraftCfgEntries> class BLACKMISC_EXPORT CAircraftCfgEntries: public BlackMisc::CValueObject<CAircraftCfgEntries>
{ {
public: public:
@@ -47,6 +45,9 @@ namespace BlackMisc
//! Default constructor //! Default constructor
CAircraftCfgEntries() = default; CAircraftCfgEntries() = default;
//! Entries representing an aircraft
CAircraftCfgEntries(const QString &fileName, int index);
//! Entries representing an aircraft //! Entries representing an aircraft
CAircraftCfgEntries(const QString &filePath, int index, const QString &title, const QString &atcType, const QString &atcModel, const QString &atcParkingCode, const QString &description); CAircraftCfgEntries(const QString &filePath, int index, const QString &title, const QString &atcType, const QString &atcModel, const QString &atcParkingCode, const QString &description);
@@ -83,26 +84,29 @@ namespace BlackMisc
//! Texture //! Texture
QString getTexture() const { return this->m_texture; } QString getTexture() const { return this->m_texture; }
//! Is Rotorcraft?
bool isRotorcraft() const { return m_rotorcraft; }
//! Manufacturer + type //! Manufacturer + type
QString getUiCombinedDescription() const; QString getUiCombinedDescription() const;
//! Filepath //! Filepath
void setFileName(const QString &filePath) { this->m_fileName = filePath; } void setFileName(const QString &filePath);
//! Title //! Title
void setTitle(const QString &title) { this->m_title = title; } void setTitle(const QString &title);
//! Index //! Index
void setIndex(int index) { this->m_index = index; } void setIndex(int index) { this->m_index = index; }
//! ATC model //! ATC model
void setAtcModel(const QString &atcModel) { this->m_atcModel = atcModel.trimmed(); } void setAtcModel(const QString &atcModel);
//! ATC type //! ATC type
void setAtcType(const QString &atcType) { this->m_atcType = atcType.trimmed(); } void setAtcType(const QString &atcType);
//! Parking code //! Parking code
void setAtcParkingCode(const QString &parkingCode) { this->m_atcParkingCode = parkingCode.trimmed(); } void setAtcParkingCode(const QString &parkingCode);
//! Description //! Description
void setDescription(const QString &description) { this->m_description = description.trimmed(); } void setDescription(const QString &description) { this->m_description = description.trimmed(); }
@@ -116,6 +120,9 @@ namespace BlackMisc
//! UI manufacturer (e.g. Airbus) //! UI manufacturer (e.g. Airbus)
void setUiManufacturer(const QString &manufacturer) { this->m_uiManufacturer = manufacturer.trimmed(); } void setUiManufacturer(const QString &manufacturer) { this->m_uiManufacturer = manufacturer.trimmed(); }
//! Is Rotorcraft?
void setRotorcraft(bool isRotorcraft) { m_rotorcraft = isRotorcraft; }
//! To aircraft model //! To aircraft model
BlackMisc::Simulation::CAircraftModel toAircraftModel() const; BlackMisc::Simulation::CAircraftModel toAircraftModel() const;
@@ -133,20 +140,21 @@ namespace BlackMisc
private: private:
BLACK_ENABLE_TUPLE_CONVERSION(CAircraftCfgEntries) BLACK_ENABLE_TUPLE_CONVERSION(CAircraftCfgEntries)
int m_index; //!< current index in given config int m_index; //!< current index in given config
QString m_fileName; //!< file name of aircraft.cfg QString m_fileName; //!< file name of .cfg
QString m_title; //!< Title in aircraft.cfg QString m_title; //!< Title in .cfg
QString m_atcType; //!< ATC type QString m_atcType; //!< ATC type
QString m_atcModel; //!< ATC model QString m_atcModel; //!< ATC model
QString m_atcParkingCode; //!< ATC parking code QString m_atcParkingCode; //!< ATC parking code
QString m_description; //!< descriptive text QString m_description; //!< descriptive text
QString m_uiType; //!< e.g. A321-231 IAE QString m_uiType; //!< e.g. A321-231 IAE
QString m_uiManufacturer; //!< e.g. Airbus QString m_uiManufacturer; //!< e.g. Airbus
QString m_texture; //!< texture, needed to identify thumbnail.jpg QString m_texture; //!< texture, needed to identify thumbnail.jpg
bool m_rotorcraft = false; //!< hint if rotorcraft
}; };
} } // ns
} } // ns
} } // ns
BLACK_DECLARE_TUPLE_CONVERSION(BlackMisc::Simulation::FsCommon::CAircraftCfgEntries, (o.m_index, o.m_fileName, o.m_title, o.m_atcType, o.m_atcModel, o.m_atcParkingCode)) BLACK_DECLARE_TUPLE_CONVERSION(BlackMisc::Simulation::FsCommon::CAircraftCfgEntries, (o.m_index, o.m_fileName, o.m_title, o.m_atcType, o.m_atcModel, o.m_atcParkingCode))
Q_DECLARE_METATYPE(BlackMisc::Simulation::FsCommon::CAircraftCfgEntries) Q_DECLARE_METATYPE(BlackMisc::Simulation::FsCommon::CAircraftCfgEntries)

View File

@@ -21,24 +21,38 @@ namespace BlackMisc
{ {
namespace Simulation namespace Simulation
{ {
namespace FsCommon namespace FsCommon
{ {
int CAircraftCfgEntriesList::read()
{
if (this->m_readForDirectory) { return this->size(); }
// not read so far, read it
this->clear();
this->m_readForDirectory = true;
return this->read(this->m_rootDirectory, excludeDirectories());
}
bool CAircraftCfgEntriesList::changeDirectory(const QString &directory)
{
if (this->m_rootDirectory != directory)
{
this->m_rootDirectory = directory;
this->m_readForDirectory = false;
}
return (!directory.isEmpty() && this->existsDir(directory));
}
/*
* Does the directory exist?
*/
bool CAircraftCfgEntriesList::existsDir(const QString &directory) const bool CAircraftCfgEntriesList::existsDir(const QString &directory) const
{ {
QString d = directory.isEmpty() ? this->m_rootDirectory : directory; QString d = directory.isEmpty() ? this->m_rootDirectory : directory;
if (d.isEmpty()) { return false; } if (d.isEmpty()) { return false; }
QDir dir(d); QDir dir(d);
//! \todo not available network dir can make this hang here //! \todo unavailable network dir can make this hang here
return dir.exists(); return dir.exists();
} }
/*
* Model for title
*/
bool CAircraftCfgEntriesList::containsModelWithTitle(const QString &title, Qt::CaseSensitivity caseSensitivity) bool CAircraftCfgEntriesList::containsModelWithTitle(const QString &title, Qt::CaseSensitivity caseSensitivity)
{ {
if (title.isEmpty()) { return false; } if (title.isEmpty()) { return false; }
@@ -47,9 +61,6 @@ namespace BlackMisc
); );
} }
/*
* Double titles
*/
QStringList CAircraftCfgEntriesList::detectAmbiguousTitles() const QStringList CAircraftCfgEntriesList::detectAmbiguousTitles() const
{ {
QStringList titles = this->getTitles(true); QStringList titles = this->getTitles(true);
@@ -70,9 +81,6 @@ namespace BlackMisc
return ambiguousTitles; return ambiguousTitles;
} }
/*
* All titles
*/
QStringList CAircraftCfgEntriesList::getTitles(bool sorted) const QStringList CAircraftCfgEntriesList::getTitles(bool sorted) const
{ {
QStringList titles = this->transform(Predicates::MemberTransform(&CAircraftCfgEntries::getTitle)); QStringList titles = this->transform(Predicates::MemberTransform(&CAircraftCfgEntries::getTitle));
@@ -80,9 +88,6 @@ namespace BlackMisc
return titles; return titles;
} }
/*
* As model list
*/
CAircraftModelList CAircraftCfgEntriesList::toAircraftModelList() const CAircraftModelList CAircraftCfgEntriesList::toAircraftModelList() const
{ {
CAircraftModelList ml; CAircraftModelList ml;
@@ -93,18 +98,24 @@ namespace BlackMisc
return ml; return ml;
} }
/*
* Models for title
*/
CAircraftCfgEntriesList CAircraftCfgEntriesList::findByTitle(const QString &title, Qt::CaseSensitivity caseSensitivity) const CAircraftCfgEntriesList CAircraftCfgEntriesList::findByTitle(const QString &title, Qt::CaseSensitivity caseSensitivity) const
{ {
return this->findBy([ = ](const CAircraftCfgEntries & entries) -> bool return this->findBy([ = ](const CAircraftCfgEntries & entries) -> bool
{ return title.compare(entries.getTitle(), caseSensitivity) == 0; }); { return title.compare(entries.getTitle(), caseSensitivity) == 0; });
} }
/* const QStringList &CAircraftCfgEntriesList::excludeDirectories()
* Read all entrities in given directory {
*/ static const QStringList exclude
{
// "SimObjects/Misc",
"SimObjects/Animals",
"SimObjects/GroundVehicles",
"SimObjects/Boats"
};
return exclude;
}
int CAircraftCfgEntriesList::read(const QString &directory, const QStringList &excludeDirectories) int CAircraftCfgEntriesList::read(const QString &directory, const QStringList &excludeDirectories)
{ {
if (m_cancelRead) { return -1; } if (m_cancelRead) { return -1; }
@@ -121,104 +132,168 @@ namespace BlackMisc
// set directory with name filters, get aircraft.cfg and sub directories // set directory with name filters, get aircraft.cfg and sub directories
QDir dir(directory, "aircraft.cfg", QDir::Name, QDir::Files | QDir::AllDirs); QDir dir(directory, "aircraft.cfg", QDir::Name, QDir::Files | QDir::AllDirs);
if (!dir.exists()) return 0; // can happen if there are shortcuts or linked dirs not available if (!dir.exists()) return 0; // can happen if there are shortcuts or linked dirs not available
int counter = 0; int counter = 0;
QString currentDir = dir.absolutePath(); QString currentDir = dir.absolutePath();
// Dirs last is crucial,since I will break recursion on "aircraft.cfg" level // Dirs last is crucial, since I will break recursion on ".cfg" level
// once I have found .cfg, I do not go any deeper
QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::AllDirs, QDir::DirsLast); QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::AllDirs, QDir::DirsLast);
for (const QFileInfo &file : files) for (const QFileInfo &file : files)
{ {
if (m_cancelRead) { return -1; } if (m_cancelRead) { return -1; }
if (file.isDir()) if (file.isDir())
{ {
QString nextDir = file.absoluteFilePath(); QString nextDir = file.absoluteFilePath();
if (currentDir.startsWith(nextDir, Qt::CaseInsensitive)) continue; // do not go up if (currentDir.startsWith(nextDir, Qt::CaseInsensitive)) { continue; } // do not go up
if (dir == currentDir) { continue; } // do not recursively call same directory if (dir == currentDir) { continue; } // do not recursively call same directory
counter += CAircraftCfgEntriesList::read(nextDir, excludeDirectories); counter += CAircraftCfgEntriesList::read(nextDir, excludeDirectories);
} }
else else
{ {
// due to the filter we expect only "aircraft.cfg" here // due to the filter we expect only ".cfg" here
QString path = file.absoluteFilePath(); // remark: in a 1st version I have used QSettings to parse to file as ini file
// unfortunately some files are malformed which could end up in wrong data
// I abuse the QSettings as ini-file reader QString fileName = file.absoluteFilePath();
QSettings aircraftCfg(path, QSettings::IniFormat); QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text))
// from the general section
const QString atcType = aircraftCfg.value("atc_type").toString();
const QString atcModel = aircraftCfg.value("atc_model").toString();
int index = 0;
while (index >= 0)
{ {
if (m_cancelRead) { return -1; } CLogMessage(this).warning("Unable to read file %1") << fileName;
QString group = QString("fltsim.%1").arg(index); continue;
aircraftCfg.beginGroup(group);
// does group exist?
if (aircraftCfg.contains("title"))
{
QString title = fixedStringContent(aircraftCfg, "title");
if (!title.isEmpty())
{
CAircraftCfgEntries entry(path, index, title, atcType, atcModel, "", "");
entry.setAtcParkingCode(fixedStringContent(aircraftCfg, "atc_parking_codes"));
entry.setDescription(fixedStringContent(aircraftCfg, "description"));
entry.setUiManufacturer(fixedStringContent(aircraftCfg, "ui_manufacturer"));
entry.setUiType(fixedStringContent(aircraftCfg, "ui_type"));
entry.setTexture(fixedStringContent(aircraftCfg, "texture"));
this->push_back(entry);
}
else
{
CLogMessage(this).info("FSX model in %1, index %2 has no title") << path << index;
}
++index;
++counter;
}
else
{
// marks end of the "fltsim.x" groups
index = -1;
}
aircraftCfg.endGroup();
} }
break; QTextStream in(&file);
} QList<CAircraftCfgEntries> tempEntries;
}
// parse through the file
QString atcType;
QString atcModel;
QString fltSection("[FLTSIM.0]");
int fltsimCounter = 0;
FileSection currentSection = Unknown;
bool isRotorcraftPath = fileName.toLower().contains("rotorcraft");
while (!in.atEnd())
{
const QString lineFixed(in.readLine().trimmed());
if (lineFixed.isEmpty()) { continue; }
if (lineFixed.startsWith("["))
{
if (lineFixed.startsWith("[GENERAL]", Qt::CaseInsensitive)) { currentSection = General; continue; }
if (lineFixed.startsWith(fltSection, Qt::CaseInsensitive))
{
CAircraftCfgEntries e(fileName, fltsimCounter);
if (isRotorcraftPath)
{
e.setRotorcraft(true);
}
tempEntries.append(e);
currentSection = Fltsim;
fltSection = QString("[FLTSIM.%1]").arg(++fltsimCounter);
continue;
}
currentSection = Unknown;
continue;
}
switch (currentSection)
{
case General:
{
if (lineFixed.startsWith("//")) { break; }
if (atcType.isEmpty() || atcModel.isEmpty())
{
QString c = getFixedIniLineContent(lineFixed);
if (lineFixed.startsWith("atc_type", Qt::CaseInsensitive)) { atcType = c; }
else if (lineFixed.startsWith("atc_model", Qt::CaseInsensitive)) { atcModel = c; }
}
}
break;
case Fltsim:
{
if (lineFixed.startsWith("//")) { break; }
CAircraftCfgEntries &e = tempEntries[tempEntries.size() - 1];
if (lineFixed.startsWith("atc_parking_codes", Qt::CaseInsensitive))
{
e.setAtcParkingCode(getFixedIniLineContent(lineFixed));
}
else if (lineFixed.startsWith("description", Qt::CaseInsensitive))
{
e.setDescription(getFixedIniLineContent(lineFixed));
}
else if (lineFixed.startsWith("ui_manufacturer", Qt::CaseInsensitive))
{
e.setUiManufacturer(getFixedIniLineContent(lineFixed));
}
else if (lineFixed.startsWith("ui_typerole", Qt::CaseInsensitive))
{
bool r = getFixedIniLineContent(lineFixed).toLower().contains("rotor");
e.setRotorcraft(r);
}
else if (lineFixed.startsWith("ui_type", Qt::CaseInsensitive))
{
e.setUiType(getFixedIniLineContent(lineFixed));
}
else if (lineFixed.startsWith("texture", Qt::CaseInsensitive))
{
e.setTexture(getFixedIniLineContent(lineFixed));
}
else if (lineFixed.startsWith("title", Qt::CaseInsensitive))
{
e.setTitle(getFixedIniLineContent(lineFixed));
}
}
break;
default:
case Unknown: break;
}
} // all lines
file.close();
// store all entries
for (const CAircraftCfgEntries &e : tempEntries)
{
if (e.getTitle().isEmpty())
{
CLogMessage(this).info("FSX model in %1, index %2 has no title") << fileName << e.getIndex();
continue;
}
CAircraftCfgEntries newEntries(e);
newEntries.setAtcModel(atcModel);
newEntries.setAtcType(atcType);
this->push_back(newEntries);
counter++;
}
return counter; // do not go any deeper in file tree, we found aircraft.cfg
} // file, no directory
} // files
return counter; return counter;
} }
QString CAircraftCfgEntriesList::fixedStringContent(const QSettings &settings, const QString &key) QString CAircraftCfgEntriesList::getFixedIniLineContent(const QString &line)
{ {
return fixedStringContent(settings.value(key)); if (line.isEmpty()) { return ""; }
int index = line.indexOf('=');
if (index < 0) { return ""; }
if (line.length() < index + 1) { return ""; }
QString content(line.mid(index + 1).trimmed());
// fix "" strings, some are malformed and just contain " at beginning, end
if (content.endsWith('"')) { content.remove(content.size() - 1 , 1); }
if (content.startsWith('"')) { content.remove(0 , 1); }
// fix C style linebreaks
content.replace("\\n", " ");
content.replace("\\t", " ");
// return
return content;
} }
QString CAircraftCfgEntriesList::fixedStringContent(const QVariant &qv)
{
if (qv.isNull() || !qv.isValid())
{
return ""; // normal when there is no settings value
}
else if (static_cast<QMetaType::Type>(qv.type()) == QMetaType::QStringList)
{
QStringList l = qv.toStringList();
return l.join(",").trimmed();
}
else if (static_cast<QMetaType::Type>(qv.type()) == QMetaType::QString)
{
return qv.toString().trimmed();
}
Q_ASSERT(false);
return "";
}
/*
* Register metadata
*/
void CAircraftCfgEntriesList::registerMetadata() void CAircraftCfgEntriesList::registerMetadata()
{ {
qRegisterMetaType<BlackMisc::CSequence<CAircraftCfgEntries>>(); qRegisterMetaType<BlackMisc::CSequence<CAircraftCfgEntries>>();

View File

@@ -39,26 +39,10 @@ namespace BlackMisc
CAircraftCfgEntriesList(const QString &rootDirectory = "") : m_rootDirectory(rootDirectory) {} CAircraftCfgEntriesList(const QString &rootDirectory = "") : m_rootDirectory(rootDirectory) {}
//! Read all aircraft.cfg files starting from root directory //! Read all aircraft.cfg files starting from root directory
int read() int read();
{
if (this->m_readForDirectory) { return this->size(); }
// not read so far, read it
this->clear();
this->m_readForDirectory = true;
return this->read(this->m_rootDirectory, excludeDirectories());
}
//! Change the directory //! Change the directory
bool changeDirectory(const QString &directory) bool changeDirectory(const QString &directory);
{
if (this->m_rootDirectory != directory)
{
this->m_rootDirectory = directory;
this->m_readForDirectory = false;
}
return (!directory.isEmpty() && this->existsDir(directory));
}
//! Virtual destructor //! Virtual destructor
virtual ~CAircraftCfgEntriesList() {} virtual ~CAircraftCfgEntriesList() {}
@@ -97,17 +81,7 @@ namespace BlackMisc
void convertFromQVariant(const QVariant &variant) { BlackMisc::setFromQVariant(this, variant); } void convertFromQVariant(const QVariant &variant) { BlackMisc::setFromQVariant(this, variant); }
//! Do not include the following directories for FS //! Do not include the following directories for FS
static const QStringList &excludeDirectories() static const QStringList &excludeDirectories();
{
static const QStringList exclude
{
"SimObjects/Animals",
"SimObjects/Misc",
"SimObjects/GroundVehicles",
"SimObjects/Boats"
};
return exclude;
}
//! Register metadata //! Register metadata
static void registerMetadata(); static void registerMetadata();
@@ -120,11 +94,16 @@ namespace BlackMisc
//! Read all entries in one directory //! Read all entries in one directory
int read(const QString &directory, const QStringList &excludeDirectories = QStringList()); int read(const QString &directory, const QStringList &excludeDirectories = QStringList());
//! Fix the content read //! Section within file
static QString fixedStringContent(const QVariant &qv); enum FileSection
{
General,
Fltsim,
Unknown
};
//! Value from settings, fixed string //! Content after "="
static QString fixedStringContent(const QSettings &settings, const QString &key); static QString getFixedIniLineContent(const QString &line);
}; };
} // namespace } // namespace
} // namespace } // namespace