From 00893b3cfc68f12c09e84643d255044a488b0eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 22 Jan 2013 05:56:12 +0100 Subject: [PATCH] Instance model (or at least something like it) --- CMakeLists.txt | 6 +- data/inifile.cpp | 17 +- data/instancebase.cpp | 64 ++- data/instancebase.h | 20 +- data/instancelist.cpp | 52 --- data/instancelist.h | 38 -- data/instancemodel.cpp | 403 ++++++++++++++++++ data/instancemodel.h | 137 ++++++ gui/mainwindow.cpp | 5 +- gui/mainwindow.h | 4 +- gui/mainwindow.ui | 30 +- .../property_tree/detail/json_parser_read.hpp | 333 +++++++++++++++ 12 files changed, 1002 insertions(+), 107 deletions(-) delete mode 100644 data/instancelist.cpp delete mode 100644 data/instancelist.h create mode 100644 data/instancemodel.cpp create mode 100644 data/instancemodel.h create mode 100644 hacks/boost/property_tree/detail/json_parser_read.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 370e01d0d..05513f2c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ IF(${BIGENDIAN}) ADD_DEFINITIONS(-DMULTIMC_BIG_ENDIAN) ENDIF(${BIGENDIAN}) +# First, include header overrides +include_directories(hacks) #### Find the required Qt parts #### find_package(Qt5Widgets) @@ -101,7 +103,7 @@ main.cpp data/appsettings.cpp data/inifile.cpp data/instancebase.cpp -data/instancelist.cpp +data/instancemodel.cpp data/settingsbase.cpp data/stdinstance.cpp @@ -123,7 +125,7 @@ gui/settingsdialog.h data/appsettings.h data/inifile.h data/instancebase.h -data/instancelist.h +data/instancemodel.h data/settingsbase.h data/settingsmacros.h data/settingsmacrosundef.h diff --git a/data/inifile.cpp b/data/inifile.cpp index df94e43e6..2d68caf6f 100644 --- a/data/inifile.cpp +++ b/data/inifile.cpp @@ -49,12 +49,23 @@ bool INIFile::loadFile(QString fileName) QStringList lines = in.readAll().split('\n'); for (int i = 0; i < lines.count(); i++) { + QString & lineRaw = lines[i]; // Ignore comments. - QString line = lines[i].left('#').trimmed(); + QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed(); - QString key = line.section('=', 0).trimmed(); - QVariant value(line.section('=', 1).trimmed()); + int eqPos = line.indexOf('='); + if(eqPos == -1) + continue; + QString key = line.left(eqPos).trimmed(); + QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); + QVariant value(valueStr); + /* + QString dbg = key; + dbg += " = "; + dbg += valueStr; + qDebug(dbg.toLocal8Bit()); + */ this->operator [](key) = value; } diff --git a/data/instancebase.cpp b/data/instancebase.cpp index 15dc54f49..a5ef35a24 100644 --- a/data/instancebase.cpp +++ b/data/instancebase.cpp @@ -16,6 +16,7 @@ #include "instancebase.h" #include +#include #include "../util/pathutils.h" @@ -23,10 +24,27 @@ InstanceBase::InstanceBase(QString dir, QObject *parent) : QObject(parent), rootDir(dir) { - QFileInfo cfgFile; + QFileInfo cfgFile(PathCombine(rootDir, "instance.cfg")); if (cfgFile.exists()) - config.loadFile(PathCombine(rootDir, "instance.cfg")); + { + if(!config.loadFile(cfgFile.absoluteFilePath())) + { + QString debugmsg("Can't load instance config file for instance "); + debugmsg+= getInstID(); + qDebug(debugmsg.toLocal8Bit()); + } + } + else + { + QString debugmsg("Can't find instance config file for instance "); + debugmsg+= getInstID(); + debugmsg += " : "; + debugmsg += + debugmsg+=" ... is this an instance even?"; + qDebug(debugmsg.toLocal8Bit()); + } + currentGroup = nullptr; } QString InstanceBase::getRootDir() const @@ -47,3 +65,45 @@ void InstanceBase::setInstName(QString name) { config.set("name", name); } + +QString InstanceBase::getInstID() const +{ + return QDir(rootDir).dirName(); +} + +InstanceModelItem* InstanceBase::getParent() const +{ + return currentGroup; +} + +QVariant InstanceBase::data ( int role ) const +{ + switch(role) + { + case Qt::DisplayRole: + return getInstName(); + default: + return QVariant(); + } +} +int InstanceBase::getRow() const +{ + return currentGroup->getIndexOf((InstanceBase*)this); +} + +InstanceModelItem* InstanceBase::getChild ( int index ) const +{ + return nullptr; +} +InstanceModel* InstanceBase::getModel() const +{ + return currentGroup->getModel(); +} +IMI_type InstanceBase::getModelItemType() const +{ + return IMI_Instance; +} +int InstanceBase::numChildren() const +{ + return 0; +} diff --git a/data/instancebase.h b/data/instancebase.h index 8c61aee00..fa043c5fb 100644 --- a/data/instancebase.h +++ b/data/instancebase.h @@ -20,9 +20,11 @@ #include #include "../data/inifile.h" +#include "instancemodel.h" -class InstanceBase : public QObject +class InstanceBase : public QObject, public InstanceModelItem { + friend class InstanceGroup; Q_OBJECT public: explicit InstanceBase(QString rootDir, QObject *parent = 0); @@ -32,13 +34,25 @@ public: QString getInstName() const; void setInstName(QString name); -protected: + QString getInstID() const; + virtual IMI_type getModelItemType() const; + virtual InstanceModelItem* getParent() const; + virtual int numChildren() const; + virtual InstanceModelItem* getChild ( int index ) const; + virtual InstanceModel* getModel() const; + virtual QVariant data ( int column ) const; + virtual int getRow() const; private: - QString rootDir; + void setGroup ( InstanceGroup* group ) + { + currentGroup = group; + }; + QString rootDir; INIFile config; + InstanceGroup * currentGroup; }; #endif // INSTANCEBASE_H diff --git a/data/instancelist.cpp b/data/instancelist.cpp deleted file mode 100644 index 7385ba86a..000000000 --- a/data/instancelist.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "instancelist.h" - -#include -#include - -#include "stdinstance.h" - -#include "../util/pathutils.h" - -InstanceList::InstanceList() : - QList() -{ - -} - -void InstanceList::addInstance(InstanceBase *inst) -{ - append(inst); -} - -void InstanceList::loadInstances(QString dir) -{ - qDebug("Loading instances"); - QDir instDir(dir); - QDirIterator iter(instDir); - - while (iter.hasNext()) - { - QString subDir = iter.next(); - if (QFileInfo(PathCombine(subDir, "instance.cfg")).exists()) - { - // TODO Differentiate between different instance types. - InstanceBase* inst = new StdInstance(subDir); - addInstance(inst); - } - } -} diff --git a/data/instancelist.h b/data/instancelist.h deleted file mode 100644 index 83ea48e27..000000000 --- a/data/instancelist.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef INSTANCELIST_H -#define INSTANCELIST_H - -#include - -#include "instancebase.h" - -class InstanceList : public QList -{ -public: - explicit InstanceList(); - - void addInstance(InstanceBase *inst); - - void loadInstances(QString dir); - -signals: - -public slots: - -}; - -#endif // INSTANCELIST_H diff --git a/data/instancemodel.cpp b/data/instancemodel.cpp new file mode 100644 index 000000000..239be5c50 --- /dev/null +++ b/data/instancemodel.cpp @@ -0,0 +1,403 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "instancemodel.h" + +#include +#include +#include +#include "stdinstance.h" + +#include "../util/pathutils.h" + +#include +#include + + +#define GROUP_FILE_FORMAT_VERSION 1 + +InstanceModel::InstanceModel( QObject* parent ) : + QAbstractItemModel() +{ +} + +InstanceModel::~InstanceModel() +{ + saveGroupInfo(); + for(int i = 0; i < groups.size(); i++) + { + delete groups[i]; + } +} + +void InstanceModel::addInstance( InstanceBase* inst, const QString& groupName ) +{ + auto group = getGroupByName(groupName); + group->addInstance(inst); +} + +void InstanceGroup::addInstance ( InstanceBase* inst ) +{ + instances.append(inst); + inst->setGroup(this); + // TODO: notify model. +} + + +void InstanceModel::initialLoad(QString dir) +{ + groupFile = dir + "/instgroups.json"; + implicitGroup = new InstanceGroup("Ungrouped", this); + groups.append(implicitGroup); + + // temporary map from instance ID to group name + QMap groupMap; + + using namespace boost::property_tree; + ptree pt; + + try + { + read_json(groupFile.toStdString(), pt); + + if (pt.get_optional("formatVersion") != GROUP_FILE_FORMAT_VERSION) + { + // TODO: Discard old formats. + } + + BOOST_FOREACH(const ptree::value_type& vp, pt.get_child("groups")) + { + ptree gPt = vp.second; + QString groupName = QString::fromUtf8(vp.first.c_str()); + + InstanceGroup *group = new InstanceGroup(groupName, this); + groups.push_back(group); + + if (gPt.get_child_optional("hidden")) + group->setHidden(gPt.get("hidden")); + + QVector groupInstances; + BOOST_FOREACH(const ptree::value_type& v, gPt.get_child("instances")) + { + QString key = QString::fromUtf8(v.second.data().c_str()); + groupMap[key] = groupName; + } + } + } + catch (json_parser_error e) + { + qDebug("Failed to read group list. JSON parser error."); +// wxLogError(_(), +// e.line(), wxStr(e.message()).c_str()); + } + catch (ptree_error e) + { + qDebug("Failed to read group list. Unknown ptree error."); + } + + qDebug("Loading instances"); + QDir instDir(dir); + QDirIterator iter(instDir); + + while (iter.hasNext()) + { + QString subDir = iter.next(); + if (QFileInfo(PathCombine(subDir, "instance.cfg")).exists()) + { + // TODO Differentiate between different instance types. + InstanceBase* inst = new StdInstance(subDir); + QString instID = inst->getInstID(); + auto iter = groupMap.find(instID); + if(iter != groupMap.end()) + { + addInstance(inst,iter.value()); + } + else + { + addInstance(inst); + } + } + } +} + +int InstanceModel::columnCount ( const QModelIndex& parent ) const +{ + // for now... + return 1; +} + +QVariant InstanceModel::data ( const QModelIndex& index, int role ) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + InstanceModelItem *item = static_cast(index.internalPointer()); + + return item->data(index.column()); +} + +QModelIndex InstanceModel::index ( int row, int column, const QModelIndex& parent ) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + InstanceModelItem *parentItem; + + if (!parent.isValid()) + parentItem = (InstanceModelItem *) this; + else + parentItem = static_cast(parent.internalPointer()); + + InstanceModelItem *childItem = parentItem->getChild(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); + +} + +QModelIndex InstanceModel::parent ( const QModelIndex& index ) const +{ + if (!index.isValid()) + return QModelIndex(); + + InstanceModelItem *childItem = static_cast(index.internalPointer()); + InstanceModelItem *parentItem = childItem->getParent(); + + if (parentItem == this) + return QModelIndex(); + + return createIndex(parentItem->getRow(), 0, parentItem); +} + +int InstanceModel::rowCount ( const QModelIndex& parent ) const +{ + InstanceModelItem *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = (InstanceModelItem*) this; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->numChildren(); +} + +bool InstanceModel::saveGroupInfo() const +{ + /* + using namespace boost::property_tree; + ptree pt; + + pt.put("formatVersion", GROUP_FILE_FORMAT_VERSION); + + try + { + typedef QMap > GroupListMap; + + GroupListMap groupLists; + for (auto iter = instances.begin(); iter != instances.end(); iter++) + { + InstanceGroup *group = getInstanceGroup(*iter); + + if (group != nullptr) + groupLists[group].push_back(*iter); + } + + ptree groupsPtree; + for (auto iter = groupLists.begin(); iter != groupLists.end(); iter++) + { + auto group = iter.key(); + auto & gList = iter.value(); + + ptree groupTree; + + groupTree.put("hidden", group->isHidden()); + + ptree instList; + for (auto iter2 = gList.begin(); iter2 != gList.end(); iter2++) + { + std::string instID((*iter2)->getInstID().toUtf8()); + instList.push_back(std::make_pair("", ptree(instID))); + } + groupTree.put_child("instances", instList); + + groupsPtree.push_back(std::make_pair(std::string(group->getName().toUtf8()), groupTree)); + } + pt.put_child("groups", groupsPtree); + + write_json(groupFile.toStdString(), pt); + } + catch (json_parser_error e) + { +// wxLogError(_("Failed to read group list.\nJSON parser error at line %i: %s"), +// e.line(), wxStr(e.message()).c_str()); + return false; + } + catch (ptree_error e) + { +// wxLogError(_("Failed to save group list. Unknown ptree error.")); + return false; + } + + return true; + */ + return false; +} + +void InstanceModel::setInstanceGroup ( InstanceBase* inst, const QString& groupName ) +{ + /* + InstanceGroup *prevGroup = getInstanceGroup(inst); + + if (prevGroup != nullptr) + { + groupsMap.remove(inst); + } + + if (!groupName.isEmpty()) + { + InstanceGroup *newGroup = nullptr; + + for (auto iter = root->groups.begin(); iter != root->groups.end(); iter++) + { + if ((*iter)->getName() == groupName) + { + newGroup = *iter; + } + } + + if (newGroup == nullptr) + { + newGroup = new InstanceGroup(groupName, this); + root->groups.push_back(newGroup); + } + + groupsMap[inst] = newGroup; + } + + // TODO: propagate change, reflect in model, etc. + //InstanceGroupChanged(inst); + */ +} + +InstanceGroup* InstanceModel::getGroupByName ( const QString& name ) const +{ + for (auto iter = groups.begin(); iter != groups.end(); iter++) + { + if ((*iter)->getName() == name) + return *iter; + } + return nullptr; +} +/* +void InstanceModel::setGroupFile ( QString filename ) +{ + groupFile = filename; +}*/ + +int InstanceModel::numChildren() const +{ + return groups.count(); +} + +InstanceModelItem* InstanceModel::getChild ( int index ) const +{ + return groups[index]; +} + +QVariant InstanceModel::data ( int role ) const +{ + switch(role) + { + case Qt::DisplayRole: + return "name"; + } + return QVariant(); +} + + +InstanceGroup::InstanceGroup(const QString& name, InstanceModel *parent) +{ + this->name = name; + this->model = parent; + this->hidden = false; +} + +InstanceGroup::~InstanceGroup() +{ + for(int i = 0; i < instances.size(); i++) + { + delete instances[i]; + } +} + + +QString InstanceGroup::getName() const +{ + return name; +} + +void InstanceGroup::setName(const QString& name) +{ + this->name = name; + //TODO: propagate change +} + +InstanceModelItem* InstanceGroup::getParent() const +{ + return model; +} + +bool InstanceGroup::isHidden() const +{ + return hidden; +} + +void InstanceGroup::setHidden(bool hidden) +{ + this->hidden = hidden; + //TODO: propagate change +} + +int InstanceGroup::getRow() const +{ + return model->getIndexOf( this); +} + +InstanceModelItem* InstanceGroup::getChild ( int index ) const +{ + return instances[index]; +} + +int InstanceGroup::numChildren() const +{ + return instances.size(); +} + +QVariant InstanceGroup::data ( int role ) const +{ + switch(role) + { + case Qt::DisplayRole: + return name; + default: + return QVariant(); + } +} \ No newline at end of file diff --git a/data/instancemodel.h b/data/instancemodel.h new file mode 100644 index 000000000..7b6cb8c69 --- /dev/null +++ b/data/instancemodel.h @@ -0,0 +1,137 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INSTANCELIST_H +#define INSTANCELIST_H + +#include +#include +#include +#include + +enum IMI_type +{ + IMI_Root, + IMI_Group, + IMI_Instance +}; + +class InstanceModel; +class InstanceGroup; +class InstanceBase; + +class InstanceModelItem +{ + public: + virtual IMI_type getModelItemType() const = 0; + virtual InstanceModelItem * getParent() const = 0; + virtual int numChildren() const = 0; + virtual InstanceModelItem * getChild(int index) const = 0; + virtual InstanceModel * getModel() const = 0; + virtual QVariant data(int role) const = 0; + virtual int getRow() const = 0; +}; + +class InstanceGroup : public InstanceModelItem +{ +public: + InstanceGroup(const QString& name, InstanceModel * model); + ~InstanceGroup(); + + QString getName() const; + void setName(const QString& name); + + bool isHidden() const; + void setHidden(bool hidden); + + virtual IMI_type getModelItemType() const + { + return IMI_Group; + } + virtual InstanceModelItem* getParent() const; + virtual InstanceModelItem* getChild ( int index ) const; + virtual int numChildren() const; + virtual InstanceModel * getModel() const + { + return model; + }; + virtual QVariant data ( int column ) const; + int getIndexOf(InstanceBase * inst) + { + return instances.indexOf(inst); + }; + virtual int getRow() const; + void addInstance ( InstanceBase* inst ); +protected: + QString name; + InstanceModel * model; + QVector instances; + bool hidden; + int row; +}; + +class InstanceModel : public QAbstractItemModel, public InstanceModelItem +{ +public: + explicit InstanceModel(QObject *parent = 0); + ~InstanceModel(); + + virtual int columnCount ( const QModelIndex& parent = QModelIndex() ) const; + virtual QVariant data ( const QModelIndex& index, int role = Qt::DisplayRole ) const; + virtual QModelIndex index ( int row, int column, const QModelIndex& parent = QModelIndex() ) const; + virtual QModelIndex parent ( const QModelIndex& child ) const; + virtual int rowCount ( const QModelIndex& parent = QModelIndex() ) const; + + void addInstance(InstanceBase *inst, const QString& groupName = "Ungrouped"); + void setInstanceGroup(InstanceBase *inst, const QString & groupName); + InstanceGroup* getGroupByName(const QString & name) const; + + void initialLoad(QString dir); + bool saveGroupInfo() const; + + virtual IMI_type getModelItemType() const + { + return IMI_Root; + } + virtual InstanceModelItem * getParent() const + { + return nullptr; + }; + virtual int numChildren() const; + virtual InstanceModelItem* getChild ( int index ) const; + virtual InstanceModel* getModel() const + { + return nullptr; + }; + virtual QVariant data ( int column ) const; + int getIndexOf(const InstanceGroup * grp) const + { + return groups.indexOf((InstanceGroup *) grp); + }; + virtual int getRow() const + { + return 0; + }; +signals: + +public slots: + +private: + QString groupFile; + QVector groups; + InstanceGroup * implicitGroup; +}; + +#endif // INSTANCELIST_H diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 8f7372c8e..87a1dcc01 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -26,7 +26,8 @@ MainWindow::MainWindow(QWidget *parent) : ui(new Ui::MainWindow) { ui->setupUi(this); - instList.loadInstances("instances"); + instList.initialLoad("instances"); + ui->instanceView->setModel(&instList); } MainWindow::~MainWindow() @@ -46,7 +47,7 @@ void MainWindow::on_actionViewInstanceFolder_triggered() void MainWindow::on_actionRefresh_triggered() { - instList.loadInstances("instances"); + instList.initialLoad("instances"); } void MainWindow::on_actionViewCentralModsFolder_triggered() diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 09cd08179..772455317 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -18,7 +18,7 @@ #include -#include "../data/instancelist.h" +#include "../data/instancemodel.h" namespace Ui { @@ -55,7 +55,7 @@ private slots: private: Ui::MainWindow *ui; - InstanceList instList; + InstanceModel instList; }; #endif // MAINWINDOW_H diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 0ef98d0ea..ccc7e777f 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -6,14 +6,38 @@ 0 0 - 600 - 400 + 739 + 657 MultiMC 5 - + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + false diff --git a/hacks/boost/property_tree/detail/json_parser_read.hpp b/hacks/boost/property_tree/detail/json_parser_read.hpp new file mode 100644 index 000000000..9a22e75bd --- /dev/null +++ b/hacks/boost/property_tree/detail/json_parser_read.hpp @@ -0,0 +1,333 @@ +// ---------------------------------------------------------------------------- +// Copyright (C) 2002-2006 Marcin Kalicinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// For more information, see www.boost.org +// ---------------------------------------------------------------------------- +#ifndef BOOST_PROPERTY_TREE_DETAIL_JSON_PARSER_READ_HPP_INCLUDED +#define BOOST_PROPERTY_TREE_DETAIL_JSON_PARSER_READ_HPP_INCLUDED + +//#define BOOST_SPIRIT_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace property_tree { namespace json_parser +{ + + /////////////////////////////////////////////////////////////////////// + // Json parser context + + template + struct context + { + + typedef typename Ptree::key_type::value_type Ch; + typedef std::basic_string Str; + typedef typename std::vector::iterator It; + + Str string; + Str name; + Ptree root; + std::vector stack; + + struct a_object_s + { + context &c; + a_object_s(context &c): c(c) { } + void operator()(Ch) const + { + if (c.stack.empty()) + c.stack.push_back(&c.root); + else + { + Ptree *parent = c.stack.back(); + Ptree *child = &parent->push_back(std::make_pair(c.name, Ptree()))->second; + c.stack.push_back(child); + c.name.clear(); + } + } + }; + + struct a_object_e + { + context &c; + a_object_e(context &c): c(c) { } + void operator()(Ch) const + { + BOOST_ASSERT(c.stack.size() >= 1); + c.stack.pop_back(); + } + }; + + struct a_name + { + context &c; + a_name(context &c): c(c) { } + void operator()(It, It) const + { + c.name.swap(c.string); + c.string.clear(); + } + }; + + struct a_string_val + { + context &c; + a_string_val(context &c): c(c) { } + void operator()(It, It) const + { + BOOST_ASSERT(c.stack.size() >= 1); + c.stack.back()->push_back(std::make_pair(c.name, Ptree(c.string))); + c.name.clear(); + c.string.clear(); + } + }; + + struct a_literal_val + { + context &c; + a_literal_val(context &c): c(c) { } + void operator()(It b, It e) const + { + BOOST_ASSERT(c.stack.size() >= 1); + c.stack.back()->push_back(std::make_pair(c.name, + Ptree(Str(b, e)))); + c.name.clear(); + c.string.clear(); + } + }; + + struct a_char + { + context &c; + a_char(context &c): c(c) { } + void operator()(It b, It e) const + { + c.string += *b; + } + }; + + struct a_escape + { + context &c; + a_escape(context &c): c(c) { } + void operator()(Ch ch) const + { + switch (ch) + { + case Ch('\"'): c.string += Ch('\"'); break; + case Ch('\\'): c.string += Ch('\\'); break; + case Ch('/'): c.string += Ch('/'); break; + case Ch('b'): c.string += Ch('\b'); break; + case Ch('f'): c.string += Ch('\f'); break; + case Ch('n'): c.string += Ch('\n'); break; + case Ch('r'): c.string += Ch('\r'); break; + case Ch('t'): c.string += Ch('\t'); break; + default: BOOST_ASSERT(0); + } + } + }; + + struct a_unicode + { + context &c; + a_unicode(context &c): c(c) { } + void operator()(unsigned long u) const + { + //u = (std::min)(u, static_cast((std::numeric_limits::max)())); + c.string += Ch(u); + } + }; + + }; + + /////////////////////////////////////////////////////////////////////// + // Json grammar + + template + struct json_grammar : + public boost::spirit::classic::grammar > + { + + typedef context Context; + typedef typename Ptree::key_type::value_type Ch; + + mutable Context c; + + template + struct definition + { + + boost::spirit::classic::rule + root, object, member, array, item, value, string, number; + boost::spirit::classic::rule< + typename boost::spirit::classic::lexeme_scanner::type> + character, escape; + + definition(const json_grammar &self) + { + + using namespace boost::spirit::classic; + // There's a boost::assertion too, so another explicit using + // here: + using boost::spirit::classic::assertion; + + // Assertions + assertion expect_root("expected object or array"); + assertion expect_eoi("expected end of input"); + assertion expect_objclose("expected ',' or '}'"); + assertion expect_arrclose("expected ',' or ']'"); + assertion expect_name("expected object name"); + assertion expect_colon("expected ':'"); + assertion expect_value("expected value"); + assertion expect_escape("invalid escape sequence"); + + // JSON grammar rules + root + = expect_root(object | array) + >> expect_eoi(end_p) + ; + + object + = ch_p('{')[typename Context::a_object_s(self.c)] + >> (ch_p('}')[typename Context::a_object_e(self.c)] + | (list_p(member, ch_p(',')) + >> expect_objclose(ch_p('}')[typename Context::a_object_e(self.c)]) + ) + ) + ; + + member + = expect_name(string[typename Context::a_name(self.c)]) + >> expect_colon(ch_p(':')) + >> expect_value(value) + ; + + array + = ch_p('[')[typename Context::a_object_s(self.c)] + >> (ch_p(']')[typename Context::a_object_e(self.c)] + | (list_p(item, ch_p(',')) + >> expect_arrclose(ch_p(']')[typename Context::a_object_e(self.c)]) + ) + ) + ; + + item + = expect_value(value) + ; + + value + = string[typename Context::a_string_val(self.c)] + | (number | str_p("true") | "false" | "null")[typename Context::a_literal_val(self.c)] + | object + | array + ; + + number + = !ch_p("-") >> + (ch_p("0") | (range_p(Ch('1'), Ch('9')) >> *digit_p)) >> + !(ch_p(".") >> +digit_p) >> + !(chset_p(detail::widen("eE").c_str()) >> + !chset_p(detail::widen("-+").c_str()) >> + +digit_p) + ; + + string + = +(lexeme_d[confix_p('\"', *character, '\"')]) + ; + + character + = (anychar_p - "\\" - "\"") + [typename Context::a_char(self.c)] + | ch_p("\\") >> expect_escape(escape) + ; + + escape + = chset_p(detail::widen("\"\\/bfnrt").c_str()) + [typename Context::a_escape(self.c)] + | 'u' >> uint_parser() + [typename Context::a_unicode(self.c)] + ; + + // Debug + BOOST_SPIRIT_DEBUG_RULE(root); + BOOST_SPIRIT_DEBUG_RULE(object); + BOOST_SPIRIT_DEBUG_RULE(member); + BOOST_SPIRIT_DEBUG_RULE(array); + BOOST_SPIRIT_DEBUG_RULE(item); + BOOST_SPIRIT_DEBUG_RULE(value); + BOOST_SPIRIT_DEBUG_RULE(string); + BOOST_SPIRIT_DEBUG_RULE(number); + BOOST_SPIRIT_DEBUG_RULE(escape); + BOOST_SPIRIT_DEBUG_RULE(character); + + } + + const boost::spirit::classic::rule &start() const + { + return root; + } + + }; + + }; + + template + unsigned long count_lines(It begin, It end) + { + return static_cast(std::count(begin, end, Ch('\n')) + 1); + } + + template + void read_json_internal(std::basic_istream &stream, + Ptree &pt, + const std::string &filename) + { + + using namespace boost::spirit::classic; + typedef typename Ptree::key_type::value_type Ch; + typedef typename std::vector::iterator It; + + // Load data into vector + std::vector v(std::istreambuf_iterator(stream.rdbuf()), + std::istreambuf_iterator()); + if (!stream.good()) + BOOST_PROPERTY_TREE_THROW(json_parser_error("read error", filename, 0)); + + // Prepare grammar + json_grammar g; + + // Parse + try + { + parse_info pi = parse(v.begin(), v.end(), g, + space_p | comment_p("//") | comment_p("/*", "*/")); + if (!pi.hit || !pi.full) + BOOST_PROPERTY_TREE_THROW((parser_error(v.begin(), "syntax error"))); + } + catch (parser_error &e) + { + BOOST_PROPERTY_TREE_THROW(json_parser_error(e.descriptor, filename, count_lines(v.begin(), e.where))); + } + + // Swap grammar context root and pt + pt.swap(g.c.root); + + } + +} } } + +#endif +