Commit 12fd1dbb authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Core/ProjectExplorer: Deprecate QDom in favour of QXmlStream-classes.

Rewrite PersistentSettingsReader/Write and CommandsFile to
use QXmlStreamReader/Writer.

Files will now be written out using native line endings and
contain proper XML version information.
Keyboard schemes will contain empty shortcut elements for empty
elements.

Rubber-stamped-by: con
parent f6c25281
......@@ -36,12 +36,39 @@
#include "command_p.h"
#include <coreplugin/uniqueidmanager.h>
#include <coreplugin/coreconstants.h>
#include <utils/qtcassert.h>
#include <QtCore/QFile>
#include <QtXml/QDomDocument>
#include <QtCore/QXmlStreamAttributes>
#include <QtCore/QXmlStreamWriter>
#include <QtCore/QXmlStreamReader>
#include <QtCore/QDebug>
#include <QtCore/QDateTime>
namespace Core {
namespace Internal {
using namespace Core;
using namespace Core::Internal;
struct Context // XML parsing context with strings.
{
Context();
const QString mappingElement;
const QString shortCutElement;
const QString idAttribute;
const QString keyElement;
const QString valueAttribute;
};
Context::Context() :
mappingElement(QLatin1String("mapping")),
shortCutElement(QLatin1String("shortcut")),
idAttribute(QLatin1String("id")),
keyElement(QLatin1String("key")),
valueAttribute(QLatin1String("value"))
{
}
/*!
\class CommandsFile
......@@ -66,29 +93,36 @@ QMap<QString, QKeySequence> CommandsFile::importCommands() const
QMap<QString, QKeySequence> result;
QFile file(m_filename);
if (!file.open(QIODevice::ReadOnly))
return result;
QDomDocument doc("KeyboardMappingScheme");
if (!doc.setContent(&file))
return result;
QDomElement root = doc.documentElement();
if (root.nodeName() != QLatin1String("mapping"))
if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
return result;
QDomElement ks = root.firstChildElement();
for (; !ks.isNull(); ks = ks.nextSiblingElement()) {
if (ks.nodeName() == QLatin1String("shortcut")) {
QString id = ks.attribute(QLatin1String("id"));
QKeySequence shortcutkey;
QDomElement keyelem = ks.firstChildElement("key");
if (!keyelem.isNull())
shortcutkey = QKeySequence(keyelem.attribute("value"));
result.insert(id, shortcutkey);
}
}
Context ctx;
QXmlStreamReader r(&file);
QString currentId;
while (!r.atEnd()) {
switch (r.readNext()) {
case QXmlStreamReader::StartElement: {
const QStringRef name = r.name();
if (name == ctx.shortCutElement) {
currentId = r.attributes().value(ctx.idAttribute).toString();
} else if (name == ctx.keyElement) {
QTC_ASSERT(!currentId.isEmpty(), return result; )
const QXmlStreamAttributes attributes = r.attributes();
if (attributes.hasAttribute(ctx.valueAttribute)) {
const QString keyString = attributes.value(ctx.valueAttribute).toString();
result.insert(currentId, QKeySequence(keyString));
} else {
result.insert(currentId, QKeySequence());
}
currentId.clear();
} // if key element
} // case QXmlStreamReader::StartElement
default:
break;
} // switch
} // while !atEnd
file.close();
return result;
}
......@@ -96,29 +130,43 @@ QMap<QString, QKeySequence> CommandsFile::importCommands() const
/*!
...
*/
bool CommandsFile::exportCommands(const QList<ShortcutItem *> &items)
{
UniqueIDManager *idmanager = UniqueIDManager::instance();
const UniqueIDManager *idmanager = UniqueIDManager::instance();
QFile file(m_filename);
if (!file.open(QIODevice::WriteOnly))
if (!file.open(QIODevice::WriteOnly|QIODevice::Text))
return false;
QDomDocument doc("KeyboardMappingScheme");
QDomElement root = doc.createElement("mapping");
doc.appendChild(root);
const Context ctx;
QXmlStreamWriter w(&file);
w.setAutoFormatting(true);
w.setAutoFormattingIndent(1); // Historical, used to be QDom.
w.writeStartDocument();
w.writeDTD(QLatin1String("<!DOCTYPE KeyboardMappingScheme>"));
w.writeComment(QString::fromAscii(" Written by Qt Creator %1, %2. ").
arg(QLatin1String(Core::Constants::IDE_VERSION_LONG),
QDateTime::currentDateTime().toString(Qt::ISODate)));
w.writeStartElement(ctx.mappingElement);
foreach (const ShortcutItem *item, items) {
QDomElement ctag = doc.createElement("shortcut");
ctag.setAttribute(QLatin1String("id"), idmanager->stringForUniqueIdentifier(item->m_cmd->id()));
root.appendChild(ctag);
QDomElement ktag = doc.createElement("key");
ktag.setAttribute(QLatin1String("value"), item->m_key.toString());
ctag.appendChild(ktag);
const QString id = idmanager->stringForUniqueIdentifier(item->m_cmd->id());
if (item->m_key.isEmpty()) {
w.writeEmptyElement(ctx.shortCutElement);
w.writeAttribute(ctx.idAttribute, id);
} else {
w.writeStartElement(ctx.shortCutElement);
w.writeAttribute(ctx.idAttribute, id);
w.writeEmptyElement(ctx.keyElement);
w.writeAttribute(ctx.valueAttribute, item->m_key.toString());
w.writeEndElement(); // Shortcut
}
}
file.write(doc.toByteArray());
w.writeEndElement();
w.writeEndDocument();
file.close();
return true;
}
} // namespace Internal
} // namespace Core
TEMPLATE = lib
TARGET = Core
DEFINES += CORE_LIBRARY
QT += xml \
network \
QT += network \
script \
sql
CONFIG += help
......
......@@ -63,7 +63,7 @@ int UniqueIDManager::uniqueIdentifier(const Id &id)
return uid;
}
QString UniqueIDManager::stringForUniqueIdentifier(int uid)
QString UniqueIDManager::stringForUniqueIdentifier(int uid) const
{
return m_uniqueIdentifiers.key(uid);
}
......
......@@ -81,7 +81,7 @@ public:
bool hasUniqueIdentifier(const Id &id) const;
int uniqueIdentifier(const Id &id);
QString stringForUniqueIdentifier(int uid);
QString stringForUniqueIdentifier(int uid) const;
private:
QHash<Id, int> m_uniqueIdentifiers;
......
......@@ -33,184 +33,375 @@
#include "persistentsettings.h"
#include <coreplugin/coreconstants.h>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QVariant>
#include <QtXml/QDomDocument>
#include <QtXml/QDomCDATASection>
#include <QtXml/QDomElement>
#include <QtCore/QStack>
#include <QtCore/QXmlStreamAttributes>
#include <QtCore/QXmlStreamReader>
#include <QtCore/QXmlStreamWriter>
#include <QtCore/QDateTime>
#include <utils/qtcassert.h>
/*!
\class ProjectExplorer::PersistentSettingsReader
\brief Reads a QVariantMap of arbitrary, nested data structures from a XML file.
Handles all string-serializable simple types and QVariantList and QVariantMap. Example:
\code
<qtcreator>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
</valuemap>
</data>
\endcode
When parsing the structure, a parse stack of ParseValueStackEntry is used for each
<data> element. ParseValueStackEntry is a variant/union of:
\list
\o simple value
\o map
\o list
\endlist
When entering a value element ( \c <value> / \c <valuelist> , \c <valuemap> ), entry is pushed
accordingly. When leaving the element, the QVariant-value of the entry is taken off the stack
and added to the stack entry below (added to list or inserted into map). The first element
of the stack is the value of the <data> element.
\sa ProjectExplorer::PersistentSettingsWriter
*/
namespace ProjectExplorer {
struct Context // Basic context containing element name string constants.
{
Context();
const QString qtCreatorElement;
const QString dataElement;
const QString variableElement;
const QString typeAttribute;
const QString valueElement;
const QString valueListElement;
const QString valueMapElement;
const QString keyAttribute;
};
Context::Context() :
qtCreatorElement(QLatin1String("qtcreator")),
dataElement(QLatin1String("data")),
variableElement(QLatin1String("variable")),
typeAttribute(QLatin1String("type")),
valueElement(QLatin1String("value")),
valueListElement(QLatin1String("valuelist")),
valueMapElement(QLatin1String("valuemap")),
keyAttribute(QLatin1String("key"))
{
}
struct ParseValueStackEntry
{
explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = QString()) : type(t), key(k) {}
explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k);
QVariant value() const;
void addChild(const QString &key, const QVariant &v);
using namespace ProjectExplorer;
QVariant::Type type;
QString key;
QVariant simpleValue;
QVariantList listValue;
QVariantMap mapValue;
};
PersistentSettingsReader::PersistentSettingsReader()
ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) :
type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue)
{
QTC_ASSERT(simpleValue.isValid(), return ; )
}
QVariant PersistentSettingsReader::restoreValue(const QString & variable) const
QVariant ParseValueStackEntry::value() const
{
if (m_valueMap.contains(variable))
return m_valueMap.value(variable);
return QVariant();
switch (type) {
case QVariant::Invalid:
return QVariant();
case QVariant::Map:
return QVariant(mapValue);
case QVariant::List:
return QVariant(listValue);
default:
break;
}
return simpleValue;
}
QVariantMap PersistentSettingsReader::restoreValues() const
void ParseValueStackEntry::addChild(const QString &key, const QVariant &v)
{
return m_valueMap;
switch (type) {
case QVariant::Map:
mapValue.insert(key, v);
break;
case QVariant::List:
listValue.push_back(v);
break;
default:
qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to "
<< QVariant::typeToName(type) << value();
break;
}
}
bool PersistentSettingsReader::load(const QString & fileName)
class ParseContext : public Context
{
m_valueMap.clear();
public:
QVariantMap parse(QFile &file);
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
return false;
private:
enum Element { QtCreatorElement, DataElement, VariableElement,
SimpleValueElement, ListValueElement, MapValueElement, UnknownElement };
QDomDocument doc;
if (!doc.setContent(&file))
return false;
Element element(const QStringRef &r) const;
static inline bool isValueElement(Element e)
{ return e == SimpleValueElement || e == ListValueElement || e == MapValueElement; }
QVariant readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const;
QDomElement root = doc.documentElement();
if (root.nodeName() != QLatin1String("qtcreator"))
return false;
bool handleStartElement(QXmlStreamReader &r);
bool handleEndElement(const QStringRef &name);
QDomElement child = root.firstChildElement();
for (; !child.isNull(); child = child.nextSiblingElement()) {
if (child.nodeName() == QLatin1String("data"))
readValues(child);
}
QStack<ParseValueStackEntry> m_valueStack;
QVariantMap m_result;
QString m_currentVariableName;
};
file.close();
return true;
QVariantMap ParseContext::parse(QFile &file)
{
QXmlStreamReader r(&file);
m_result.clear();
m_currentVariableName.clear();
while (!r.atEnd()) {
switch (r.readNext()) {
case QXmlStreamReader::StartElement:
if (handleStartElement(r))
return m_result;
break;
case QXmlStreamReader::EndElement:
if (handleEndElement(r.name()))
return m_result;
break;
case QXmlStreamReader::Invalid:
qWarning("Error reading %s:%d: %s", qPrintable(file.fileName()),
int(r.lineNumber()), qPrintable(r.errorString()));
return QVariantMap();
break;
default:
break;
} // switch token
} // while (!r.atEnd())
return m_result;
}
QVariant PersistentSettingsReader::readValue(const QDomElement &valElement) const
bool ParseContext::handleStartElement(QXmlStreamReader &r)
{
QString name = valElement.nodeName();
QString type = valElement.attribute(QLatin1String("type"));
QVariant v;
const QStringRef name = r.name();
const Element e = element(name);
if (e == VariableElement) {
m_currentVariableName = r.readElementText();
return false;
}
if (!ParseContext::isValueElement(e))
return false;
if (name == QLatin1String("value")) {
if(type == QLatin1String("QChar")) {
//Workaround: QTBUG-12345
v.setValue(QChar(valElement.text().at(0)));
} else {
v.setValue(valElement.text());
v.convert(QVariant::nameToType(type.toLatin1().data()));
}
} else if (name == QLatin1String("valuelist")) {
QDomElement child = valElement.firstChildElement();
QList<QVariant> valList;
for (; !child.isNull(); child = child.nextSiblingElement()) {
valList << readValue(child);
}
v.setValue(valList);
} else if (name == QLatin1String("valuemap")) {
QDomElement child = valElement.firstChildElement();
QMap<QString, QVariant> valMap;
for (; !child.isNull(); child = child.nextSiblingElement()) {
QString key = child.attribute(QLatin1String("key"));
valMap.insert(key, readValue(child));
}
v.setValue(valMap);
const QXmlStreamAttributes attributes = r.attributes();
const QString key = attributes.hasAttribute(keyAttribute) ?
attributes.value(keyAttribute).toString() : QString();
switch (e) {
case SimpleValueElement:
// This reads away the end element, so, handle end element right here.
m_valueStack.push_back(ParseValueStackEntry(readSimpleValue(r, attributes), key));
return handleEndElement(name);
case ListValueElement:
m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key));
break;
case MapValueElement:
m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key));
break;
default:
break;
}
return false;
}
return v;
bool ParseContext::handleEndElement(const QStringRef &name)
{
const Element e = element(name);
if (ParseContext::isValueElement(e)) {
QTC_ASSERT(!m_valueStack.isEmpty(), return true; )
const ParseValueStackEntry top = m_valueStack.pop();
if (m_valueStack.isEmpty()) { // Last element? -> Done with that variable.
QTC_ASSERT(!m_currentVariableName.isEmpty(), return true; )
m_result.insert(m_currentVariableName, top.value());
m_currentVariableName.clear();
return false;
}
m_valueStack.top().addChild(top.key, top.value());
}
return e == QtCreatorElement;
}
void PersistentSettingsReader::readValues(const QDomElement &data)
ParseContext::Element ParseContext::element(const QStringRef &r) const
{
QString variable;
QVariant v;
if (r == valueElement)
return SimpleValueElement;
if (r == valueListElement)
return ListValueElement;
if (r == valueMapElement)
return MapValueElement;
if (r == qtCreatorElement)
return QtCreatorElement;
if (r == dataElement)
return DataElement;
if (r == variableElement)
return VariableElement;
return UnknownElement;
}
QDomElement child = data.firstChildElement();
for (; !child.isNull(); child = child.nextSiblingElement()) {
if (child.nodeName() == QLatin1String("variable")) {
variable = child.text();
} else {
v = readValue(child);
}
QVariant ParseContext::readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const
{
// Simple value
const QString type = attributes.value(typeAttribute).toString();
const QString text = r.readElementText();
if (type == QLatin1String("QChar")) { // Workaround: QTBUG-12345
QTC_ASSERT(text.size() == 1, return QVariant(); )
return QVariant(QChar(text.at(0)));
}
QVariant value;
value.setValue(text);
value.convert(QVariant::nameToType(type.toLatin1().data()));
return value;
}
m_valueMap.insert(variable, v);
// =================================== PersistentSettingsReader
PersistentSettingsReader::PersistentSettingsReader()
{
}
///
/// PersistentSettingsWriter
///
QVariant PersistentSettingsReader::restoreValue(const QString & variable) const
{
if (m_valueMap.contains(variable))
return m_valueMap.value(variable);
return QVariant();
}
PersistentSettingsWriter::PersistentSettingsWriter()
QVariantMap PersistentSettingsReader::restoreValues() const
{
return m_valueMap;
}
bool PersistentSettingsReader::load(const QString & fileName)
{
m_valueMap.clear();
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
return false;
ParseContext ctx;
m_valueMap = ctx.parse(file);
file.close();
return true;
}
void PersistentSettingsWriter::writeValue(QDomElement &ps, const QVariant &variant)
/*!
\class ProjectExplorer::PersistentSettingsWriter
\brief Serializes a QVariantMap of arbitrary, nested data structures to a XML file.
\sa ProjectExplorer::PersistentSettingsReader
*/
PersistentSettingsWriter::PersistentSettingsWriter()
{
if (variant.type() == QVariant::StringList || variant.type() == QVariant::List) {
QDomElement values = ps.ownerDocument().createElement("valuelist");
values.setAttribute("type", QVariant::typeToName(QVariant::List));
QList<QVariant> varList = variant.toList();
foreach (const QVariant &var, varList) {
writeValue(values, var);
}
ps.appendChild(values);
} else if (variant.type() == QVariant::Map) {
QDomElement values = ps.ownerDocument().createElement("valuemap");
values.setAttribute("type", QVariant::typeToName(QVariant::Map));
QMap<QString, QVariant> varMap = variant.toMap();
QMap<QString, QVariant>::const_iterator i = varMap.constBegin();
while (i != varMap.constEnd()) {
writeValue(values, i.value());
values.lastChild().toElement().
setAttribute(QLatin1String("key"), i.key());
++i;
}
}