Название: QSettings + Xml = CXmlSettings
Отправлено: break от Январь 17, 2010, 06:20
выкладываю: H файл: XmlSettings.h C++ (Qt) #ifndef __XML_SETTINGS_H__ #define __XML_SETTINGS_H__ #include <QString> #include <QSettings> class QDomDocument; class QDomElement; class QIODevice; class CXmlSettings { QSettings * m_pSettings; public: CXmlSettings( QString fname = "" ); ~CXmlSettings(); inline QSettings& settings() const { return * m_pSettings; } // при использование CXmlSettings::value конфиг создастся автоматически, если его не было QVariant value( const QString & key, const QVariant & defaultValue = QVariant() ); void setValue ( const QString & key, const QVariant & value ); private: static bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map); static bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map); static void processWriteKey( QDomDocument& doc, QDomElement& domElement, QString key, const QVariant& value ); static void processReadKey( QString key, QSettings::SettingsMap &map, QDomElement& domElement ); }; #endif // __XML_SETTINGS_H__
CPP файл: XmlSettings.cpp C++ (Qt) #include "XmlSettings.h" #include "FileUtils.h" #include <QDomDocument> #include <QDebug> CXmlSettings::CXmlSettings( QString fname ) { if ( fname.isEmpty() ) fname = settingsPath() + appBaseName() + ".xml"; static const QSettings::Format XmlFormat = QSettings::registerFormat("xml", readXmlFile, writeXmlFile); m_pSettings = new QSettings( fname, XmlFormat ); } CXmlSettings::~CXmlSettings() { delete m_pSettings; } QVariant CXmlSettings::value( const QString & key, const QVariant & defaultValue ) { if ( !settings().contains( key ) ) settings().setValue( key, defaultValue ); return settings().value( key ); } void CXmlSettings::setValue ( const QString & key, const QVariant & value ) { settings().setValue( key, value ); } bool CXmlSettings::readXmlFile(QIODevice &device, QSettings::SettingsMap &map) { qDebug() << "-----readXmlSettings------"; QDomDocument doc(""); if ( !doc.setContent( &device ) ) return false; QDomElement root = doc.documentElement(); processReadKey( "", map, root ); /* // Для теста использовалось при отладке QMap<QString, QVariant>::const_iterator i = map.constBegin(); while (i != map.constEnd()) { qDebug() << i.key() << ": " << i.value() << endl; ++i; } */ return true; } bool CXmlSettings::writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map) { qDebug() << "-----writeXmlSettings-----"; /* // Для теста использовалось при отладке QMap<QString, QVariant>::const_iterator i = map.constBegin(); while (i != map.constEnd()) { qDebug() << i.key() << ": " << i.value() << endl; ++i; } */ QDomDocument doc(""); QDomElement root = doc.createElement("Main"); doc.appendChild(root); QMapIterator<QString, QVariant> i(map); while ( i.hasNext() ) { i.next(); QString sKey = i.key(); QVariant value = i.value(); processWriteKey( doc, root, sKey, i.value() ); }; QDomNode xmlNode = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\""); doc.insertBefore(xmlNode, doc.firstChild()); QTextStream out( &device ); doc.save(out, 4); return true; } void CXmlSettings::processWriteKey( QDomDocument& doc, QDomElement& domElement, QString key, const QVariant& value ) { int slashPos = key.indexOf( '/' ); // переданный ключ является параметром if ( slashPos < 0 ) { // не пишем в конфиг параметр size (является ограничением - нельзя исп. пар-тр с таким именем) if ( key == "size" ) return; domElement.setAttribute( key, value.toString() ); return; }; // получение имени группы соответствующей xml ноде QString groupName = key.left( slashPos ); // если в качестве имени использован числовой параметр - это табличная строка, преобразуем ее в row_? if ( groupName.toInt() ) { groupName = "row_" + groupName; domElement.toElement().setAttribute("table", "1"); }; // поиск/создание ноды соответствующей ключу QDomElement groupElement; QDomNode findedGroupNode = domElement.namedItem( groupName ); if ( findedGroupNode.isNull() ) { groupElement = doc.createElement( groupName ); domElement.appendChild( groupElement ); } else groupElement = findedGroupNode.toElement(); // готовим обрезанную часть ключа key = key.right( key.size() - slashPos - 1 ); // продолжение обработки (создание/поиск групп) - пока не найдется параметр processWriteKey( doc, groupElement, key, value ); } void CXmlSettings::processReadKey( QString key, QSettings::SettingsMap &map, QDomElement& domElement ) { QDomNamedNodeMap namedNodeMap = domElement.attributes(); // Добавление всех атрибутов элемента в качестве значений for (int i = 0; i < namedNodeMap.count(); ++i) { QString name = namedNodeMap.item( i ).toAttr().name(); QString value = namedNodeMap.item( i ).toAttr().value(); map.insert( key + name, value ); }; QDomNodeList nlChild = domElement.childNodes(); // если узел является таблицей - то все дети строки bool isTable = domElement.attribute("table", "0").toInt(); // создаем доп. элемент size равный числу детей (необходим для QSettings - beginArray) if ( isTable ) map.insert( key + "size", nlChild.count() ); // проход по всем детям for (int i = 0; i < nlChild.count(); ++i) { QString childName = nlChild.item(i).toElement().tagName(); if ( childName.contains("row_") ) childName = childName.right( childName.size() - 4 ); QString subKey = key + childName + "/"; QDomElement subElement = nlChild.item(i).toElement(); processReadKey( subKey, map, subElement ); }; }
Функции settingsPath() и appBaseName() - у меня реализованы в моем FileUtils - возвращают дефолтный путь к папке с настройками и базовое имя программы (без расширения) C++ (Qt) QString settingsPath() { QDir dir( QCoreApplication::applicationDirPath() + "/../Settings" ); return dir.absolutePath() + "/"; } QString appBaseName() { QString appBaseName = QCoreApplication::applicationFilePath(); QStringList sl = appBaseName.split("/"); appBaseName = sl[ sl.count()-1 ]; // убираем ".EXE" для виндовых файлов appBaseName.replace( ".exe", "", Qt::CaseInsensitive ); return appBaseName; }
Также продублировал во вложении. Пользоваться можно через обращение к settings() - всеми возможностями самого QSettings - группы, массивы и т.д. Ограничение - нельзя использовать атрибут с именем "table" - он используется для определения таблицы (массива) при чтение настроек. Если пользоваться через прямые методы value, CXmlSettings - то в случае если значения в конфиге не было - оно создастся и заполниться значением по умолчанию (сделал чтобы программа сама создавала свой конфиг если был утерян...).
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: asvil от Январь 17, 2010, 11:17
Помню весной пользовался этим кодом. Однако мне не понравилось наследование через мембер-указатель на родитель. Это же одна лишняя строка при использовании. Также не понравилось хранение значение в атрибутах. Выкладываю модификацию, которую сразу можно использовать как QSettings и значения пишуться в дочерние ноды. В свою очередь спрашиваю может кто-то реализовывал аналог QSettings для хранения настроек в sql базе?
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: zenden от Январь 17, 2010, 14:14
Вот вы молодцы а то я уже запарился искать сохранялку в XML
Название: S
Отправлено: break от Январь 17, 2010, 14:41
Однако мне не понравилось наследование через мембер-указатель на родитель. Это же одна лишняя строка при использовании. Там нет такого наследования - есть только агрегирование QSettings для реализации функционала CXmlSettings - думаю это правильно, а "лишние символы при использовании" не заслуживают наследования. Руководствовался такими принципами 1) Наследование применять только там где без него нельзя обойтись 2) CXmlSettings не "реализован посредством QSettings" а лишь использует его ф-онал, он был бы реализован посредством если бы ф-ции C++ (Qt) static bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map); static bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map);
были виртуальными - то для их реализации нужно было бы наследоваться от QSettings 3) В случае изменения исходного кода QSettings в случае агрегирования нет(или минимум) проблем 4) Если бы ф-ции C++ (Qt) static bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map); static bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map);
получали QSettings в качестве параметра не оп ссылке, а по указателю - то вообще можно было бы не инклудить <QSettings> в заголовочнике "XmlSettings.h" - то есть фактически скрытая реализация - плюс легкая замена этой скрытой реализации в случае такой необходимости... (переход на другую версию Qt) Но ваш пример тоже имеет право на существование. Правда на счет лишней писанины я не думаю что это актуально - т.к. все сегодняшние IDE работают с CodeComplition - я наоборот стараюсь давать более полные имена методов чтобы потом не путаться что он скрывает за функционал. Также не понравилось хранение значение в атрибутах. Может действительно нужен флажок для указаничя классу где хранить в атрибутах или во вложенных тегах. Иногда так действительно удобнее, но XML снастройками чуть побольше будет...
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: asvil от Январь 17, 2010, 20:11
...агрегирование... - это и имел ввиду, не владею терминологией в полной мере. XmlSettings *globalSettings = new XmlSettings() // Ну или там <componentName>Settings Теперь природный инстинкт лени мешает завести еще одну переменную, и код продолжается в таком стиле. globalSettings->settings()->setValue(x,1); А внутри setValue вызыв функции, которая тоже осмысленно именована. И вот уже нужен перевод строки и кол-во закрывающих скобочек приближается к лисп-стилю. Но это я так, не для дискуссии. Может преувеличиваю.
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: lit-uriy от Январь 18, 2010, 07:23
наследоватся надо от QSettings, иначе смысла нет в какой-либо связи с QSettings. Например есть в программе метод: void Class::setSetting(QSettings seting)
И используется оригинал QSettings, приспичило хранить в БД или XML, воткнул туда соответствующего наследника и всё.
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: break от Январь 27, 2010, 06:39
наследоватся надо от QSettings, иначе смысла нет в какой-либо связи с QSettings. Например есть в программе метод: void Class::setSetting(QSettings seting)
И используется оригинал QSettings, приспичило хранить в БД или XML, воткнул туда соответствующего наследника и всё.
И что? Воткнул туда CXmlSettings xml; xnl.settings(); ------------ указатель на QSettings и ВСЕ Сам QSettings сделан так что его не нужно наследовать!!! - мы регистрируем формат и скармливаем его в конструктор - то есть все наши хранения настроек в БД или XML обрабатывает сам QSettings через этот формат ( фактически 2 написанные нами процедуры читания, писания настроек). Я сделал CXmlSettings только для своего удобства т.к. привычно работать с классами - можно вообще использовать 2 глобальные процедуры - регистрировать их в формат и скармливать в QSettings и все будет работать - без какого-либо наследования! Теперь природный инстинкт лени мешает завести еще одну переменную, и код продолжается в таком стиле. Смею вас уверить - наследование не для того предназначено чтобы синтаксис красивее и удобнее делать - наследование должно использоваться только на основе логического анализа класса предка и потенциального наследника - а никак не анализа длинности идентификаторов членов.
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: lit-uriy от Январь 27, 2010, 08:14
>>Сам QSettings сделан так что его не нужно наследовать!!! не важно как сделан QSettings.
Простой вопрос, я могу безболезнено воткнуть твой класс в имеющийся код вместо QSettings или нет? Т.е. там где у меня было: QSettings seting; obj->(seting); можно ли сделать: CXmlSettings seting; obj->(seting); Если считать, что этот самый метод obj->(seting); использует все возможности QSettings (вызывает все public-методы)? Приведёт ли это к ожидаемому результату?
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: break от Январь 27, 2010, 21:14
не важно как сделан QSettings. в том то и дело что ВАЖНО - в нем нет никаких виртуальных фуекций предназначенных для замещения! [/quote] Простой вопрос, я могу безболезнено воткнуть твой класс в имеющийся код вместо QSettings или нет? Т.е. там где у меня было: QSettings seting; obj->(seting); можно ли сделать: CXmlSettings seting; obj->(seting); Если считать, что этот самый метод obj->(seting); использует все возможности QSettings (вызывает все public-методы)? Приведёт ли это к ожидаемому результату?
Я уже писал выше что надо использовать C++ (Qt) CXmlSettings xml; obj->( xml.settings() );
Это приведет к ожидаемому результату! Все возможности QSettings останутся в силе - и дело не в моем классе а в том что кутешники так сделали QSettings что его не нужно наследовать - достаточно зарегистрировать свой тип "НАСТРОЕК" и скормить его в конструктор QSettings (что и делает класс CXmlSettings).
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: break от Апрель 10, 2010, 00:59
Обнаружил такой момент - если создавать QSettings передавай туда имя файла который нельзя открыть (например имя каталога), то QSettings будет работать кешируя при этом пары, ключ-> значение где то в своей приватной части. Да так что даже создавая разные экземпляры QSettings в разных областях видимости эти пары сохраняются! Мне это кажется скорее глюком чем фичей. Дело в том что предполагался код в котором настройки читаются в любом случае даже если сам файл настроек не указан. Это делалось для того чтобы не писать 2 раза код инициализации большого количества переменных, а инициализировать их при чтение настроек передавая значение по умолчанию (которое присвоится в случае отсутствия конкретного параметра в настройках). Ну а так как они кешируются для неправильно указанных имен файлов - то все стало путаться. Исправлять QSettings смысла не вижу, кроме того достала проблема с его проблемой "пересортировки" ключей. Т.е. при чтение / записи произвольно менялся их порядок в файле. Написал маленький класс кот. зависит только от QtXml и работает похоже на QSettings ( есть методы beginGroup(), endGroup() ) Оказалось пользоваться даже удобнее! Можно всегда перейти к QDomElement и работать с xml напрямую, можно по старому через beginGroup(), enfGroup()? value(), beginArray() делать не стал т.к. чтение массивов делается легко так: C++ (Qt) m_xml.beginGroup( "ThresholdRU_Values" ); int nGearPos = 0; float fValueI = 0; QDomElement deItem = m_xml.currentElement().firstChildElement( "item" ); while( !deItem.isNull() ) { nGearPos = deItem.attribute( "gear_pos" ).toInt(); fValueI = deItem.attribute( "value_i" ).toDouble(); m_threshold_RU_Gear_Assign.insert( nGearPos, fValueI ); deItem = deItem.nextSiblingElement( "item" ); }; m_xml.endGroup();
сам класс C++ (Qt) #ifndef __XML_SETTINGS_H__ #define __XML_SETTINGS_H__ #include <QDomDocument> #include <QVariant> class CXml_Settings { QDomDocument m_doc; QDomElement m_rootElement; QDomElement m_currentElement; QString m_sFileName; bool m_bAutoSave; public: CXml_Settings(); CXml_Settings( QString sFName ); ~CXml_Settings(); bool loadFromFile( const QString sFName ); void saveToFile( const QString sFName ); void beginGroup( const QString sName ); void endGroup(); QVariant value( const QString & sName, const QVariant & defaultValue = QVariant() ); void setValue ( const QString & key, const QVariant & value ); inline QDomElement& currentElement() { return m_currentElement; }; inline QDomElement& rootElement() { return m_rootElement; }; inline void setAutoSave( bool bVal ) { m_bAutoSave = bVal; } private: void createRootElement(); }; #endif // __XML_SETTINGS_H__
C++ (Qt) #include "Xml_Settings.h" #include "FileUtils.h" #include "DebugLog.h" #define ROOT_NODE_NAME "Main" CXml_Settings::CXml_Settings() : m_doc("") { m_sFileName.clear(); createRootElement(); m_bAutoSave = true; } CXml_Settings::CXml_Settings( QString sFName ) : m_doc("") { m_sFileName.clear(); createRootElement(); loadFromFile( sFName ); } CXml_Settings::~CXml_Settings() { if ( ( m_bAutoSave ) && ( !m_sFileName.isEmpty() ) ) saveToFile( m_sFileName ); } bool CXml_Settings::loadFromFile( const QString sFName ) { m_doc.clear(); m_sFileName = sFName; if ( !openXMLfile( m_sFileName, QFile::ReadOnly, m_doc ) ) { createRootElement(); return false; } else { m_rootElement = m_doc.namedItem( ROOT_NODE_NAME ).toElement(); bool bNormalLoad = !m_rootElement.isNull(); if ( !bNormalLoad ) ERROR_OUT << "Can't find root XML node with name =" << ROOT_NODE_NAME; m_currentElement = m_rootElement; return bNormalLoad; }; } void CXml_Settings::saveToFile( const QString sFName ) { saveXMLfile( sFName, m_doc ); } void CXml_Settings::beginGroup( const QString sName ) { QDomNode node = m_currentElement.namedItem( sName ); if ( !node.isNull() ) m_currentElement = node.toElement(); else { QDomElement deNewElement = m_doc.createElement( sName ); m_currentElement.appendChild( deNewElement ); m_currentElement = deNewElement; }; } void CXml_Settings::endGroup() { if ( m_currentElement.nodeName() == ROOT_NODE_NAME ) return; m_currentElement = m_currentElement.parentNode().toElement(); } QVariant CXml_Settings::value( const QString & key, const QVariant & defaultValue ) { if ( m_currentElement.hasAttribute( key ) ) return QVariant( m_currentElement.attribute( key ) ); else { m_currentElement.setAttribute( key, defaultValue.toString() ); return defaultValue; } } void CXml_Settings::setValue ( const QString & key, const QVariant & value ) { m_currentElement.setAttribute( key, value.toString() ); } void CXml_Settings::createRootElement() { // QDomNode xmlNode = m_doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\""); // m_doc.insertBefore(xmlNode, m_doc.firstChild()); m_rootElement = m_doc.createElement( ROOT_NODE_NAME ); m_doc.appendChild( m_rootElement ); m_currentElement = m_rootElement; }
методы из моего FileUtils C++ (Qt) bool openXMLfile( QString fname, QFile::OpenMode mode, QDomDocument& doc ) { QFile file( fname ); if ( !file.open(mode) ) { ERROR_OUT << "Error open file: " << fname << "!!!"; return false; }; if ( !doc.setContent(&file) ) { ERROR_OUT << "Error read XML file: " << fname << "!!!"; file.close(); return false; } file.close(); return true; } bool saveXMLfile(QString fname, QDomDocument& doc) { QFile ofile( fname ); if (!ofile.open(QIODevice::WriteOnly)) { ERROR_OUT << "Can't open file to write: " << fname; return false; }; QTextStream out(&ofile); //doc.save(out, 4); out.setCodec( QTextCodec::codecForName("UTF-8") ); doc.save(out, 4, QDomNode::EncodingFromTextStream ); ofile.close(); return true; }
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: pastor от Апрель 12, 2010, 11:03
Продолжение http://www.prog.org.ru/topic_13144_0.html
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: MrLink от Июнь 03, 2010, 16:34
beginArray() делать не стал т.к. чтение массивов делается легко так:
Чтение может и легко, а вот запись... каждый сам будет дописывать? Мне, вот, понадобилось. Особой проблемы, конечно, нет. Но как-то некрасиво получается.
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: break от Июнь 03, 2010, 16:56
Запись так же легко через QDomElement - решение с QSettings и его массивами неудобно и некрасиво
Название: Re: QSettings + Xml = CXmlSettings
Отправлено: MrLink от Июнь 04, 2010, 09:36
Я предполагал такой ответ - может, тогда вообще всё сделать на QDom... QSetting я не рассматриваю (пусть каждый сам решает, подходит он для его задачи или нет). Я говорю про то, что если и использовать CXml... , то в него надо еще дописать часть, что бы его можно было использовать по аналогии с чтением. Что б потом его особенно не трогать - я его откомпилировал как статическую библиотеку...
|