forked from qt-creator/qt-creator
AndroidManifestEditor: Rewrite writing of xml files
Using QXmlStreamWriter + QXmlStreamReader gives us greater control over the ordering of attributes. That way we can ensure that we never change the ordering that the user used. (Whereas for QDom the best we can do is to have a predictable but random order.) Neither QXmlStreamWriter nor QDom allows us to control the indentation and other whitespace, so any user indentation is lost. In addtion QXmlStreamWriter seems to have several bugs with indentation though, leading to rather strangely formatted files. Task-number: QTCREATORBUG-10870 Change-Id: I4cdbdcd499227f418e7767eb1b532efbbd5083a5 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com> Reviewed-by: Alessandro Portale <alessandro.portale@digia.com>
This commit is contained in:
@@ -805,53 +805,6 @@ void AndroidManifestEditorWidget::syncToWidgets(const QDomDocument &doc)
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void setUsesSdk(QDomDocument &doc, QDomElement &manifest, int minimumSdk, int targetSdk)
|
||||
{
|
||||
QDomElement usesSdk = manifest.firstChildElement(QLatin1String("uses-sdk"));
|
||||
if (usesSdk.isNull()) { // doesn't exist yet
|
||||
if (minimumSdk == 0 && targetSdk == 0) {
|
||||
// and doesn't need to exist
|
||||
} else {
|
||||
usesSdk = doc.createElement(QLatin1String("uses-sdk"));
|
||||
if (minimumSdk != 0)
|
||||
usesSdk.setAttribute(QLatin1String("android:minSdkVersion"), minimumSdk);
|
||||
if (targetSdk != 0)
|
||||
usesSdk.setAttribute(QLatin1String("android:targetSdkVersion"), targetSdk);
|
||||
manifest.appendChild(usesSdk);
|
||||
}
|
||||
} else {
|
||||
if (minimumSdk == 0 && targetSdk == 0) {
|
||||
// We might be able to remove the whole element
|
||||
// check if there are other attributes
|
||||
QDomNamedNodeMap usesSdkAttributes = usesSdk.attributes();
|
||||
bool keepNode = false;
|
||||
for (int i = 0; i < usesSdkAttributes.size(); ++i) {
|
||||
if (usesSdkAttributes.item(i).nodeName() != QLatin1String("android:minSdkVersion")
|
||||
&& usesSdkAttributes.item(i).nodeName() != QLatin1String("android:targetSdkVersion")) {
|
||||
keepNode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keepNode) {
|
||||
usesSdk.removeAttribute(QLatin1String("android:minSdkVersion"));
|
||||
usesSdk.removeAttribute(QLatin1String("android:targetSdkVersion"));
|
||||
} else {
|
||||
manifest.removeChild(usesSdk);
|
||||
}
|
||||
} else {
|
||||
if (minimumSdk == 0)
|
||||
usesSdk.removeAttribute(QLatin1String("android:minSdkVersion"));
|
||||
else
|
||||
usesSdk.setAttribute(QLatin1String("android:minSdkVersion"), minimumSdk);
|
||||
|
||||
if (targetSdk == 0)
|
||||
usesSdk.removeAttribute(QLatin1String("android:targetSdkVersion"));
|
||||
else
|
||||
usesSdk.setAttribute(QLatin1String("android:targetSdkVersion"), targetSdk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int extractVersion(const QString &string)
|
||||
{
|
||||
if (!string.startsWith(QLatin1String("API")))
|
||||
@@ -868,78 +821,330 @@ int extractVersion(const QString &string)
|
||||
|
||||
void AndroidManifestEditorWidget::syncToEditor()
|
||||
{
|
||||
QDomDocument doc;
|
||||
if (!doc.setContent(m_textEditorWidget->toPlainText())) {
|
||||
// This should not happen
|
||||
updateInfoBar();
|
||||
return;
|
||||
QString result;
|
||||
QXmlStreamReader reader(m_textEditorWidget->toPlainText());
|
||||
reader.setNamespaceProcessing(false);
|
||||
QXmlStreamWriter writer(&result);
|
||||
writer.setAutoFormatting(true);
|
||||
writer.setAutoFormattingIndent(4);
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
if (reader.hasError()) {
|
||||
// This should not happen
|
||||
updateInfoBar();
|
||||
return;
|
||||
} else {
|
||||
if (reader.name() == QLatin1String("manifest"))
|
||||
parseManifest(reader, writer);
|
||||
else if (reader.isStartElement())
|
||||
parseUnknownElement(reader, writer);
|
||||
else
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
}
|
||||
|
||||
QDomElement manifest = doc.documentElement();
|
||||
manifest.setAttribute(QLatin1String("package"), m_packageNameLineEdit->text());
|
||||
manifest.setAttribute(QLatin1String("android:versionCode"), m_versionCode->value());
|
||||
manifest.setAttribute(QLatin1String("android:versionName"), m_versionNameLinedit->text());
|
||||
|
||||
if (!m_appNameInStringsXml) {
|
||||
QDomElement application = manifest.firstChildElement(QLatin1String("application"));
|
||||
application.setAttribute(QLatin1String("android:label"), m_appNameLineEdit->text());
|
||||
}
|
||||
|
||||
setUsesSdk(doc, manifest, extractVersion(m_androidMinSdkVersion->currentText()),
|
||||
extractVersion(m_androidTargetSdkVersion->currentText()));
|
||||
|
||||
setAndroidAppLibName(doc, manifest.firstChildElement(QLatin1String("application"))
|
||||
.firstChildElement(QLatin1String("activity")),
|
||||
m_targetLineEdit->currentText());
|
||||
|
||||
// permissions
|
||||
QDomElement permissionElem = manifest.firstChildElement(QLatin1String("uses-permission"));
|
||||
while (!permissionElem.isNull()) {
|
||||
manifest.removeChild(permissionElem);
|
||||
permissionElem = manifest.firstChildElement(QLatin1String("uses-permission"));
|
||||
}
|
||||
|
||||
foreach (const QString &permission, m_permissionsModel->permissions()) {
|
||||
permissionElem = doc.createElement(QLatin1String("uses-permission"));
|
||||
permissionElem.setAttribute(QLatin1String("android:name"), permission);
|
||||
manifest.appendChild(permissionElem);
|
||||
}
|
||||
|
||||
bool ensureIconAttribute = !m_lIconPath.isEmpty()
|
||||
|| !m_mIconPath.isEmpty()
|
||||
|| !m_hIconPath.isEmpty();
|
||||
|
||||
if (ensureIconAttribute) {
|
||||
QDomElement applicationElem = manifest.firstChildElement(QLatin1String("application"));
|
||||
applicationElem.setAttribute(QLatin1String("android:icon"), QLatin1String("@drawable/icon"));
|
||||
}
|
||||
|
||||
|
||||
QString newText = doc.toString(4);
|
||||
if (newText == m_textEditorWidget->toPlainText())
|
||||
if (result == m_textEditorWidget->toPlainText())
|
||||
return;
|
||||
|
||||
m_textEditorWidget->setPlainText(newText);
|
||||
m_textEditorWidget->setPlainText(result);
|
||||
m_textEditorWidget->document()->setModified(true);
|
||||
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
bool AndroidManifestEditorWidget::setAndroidAppLibName(QDomDocument document, QDomElement activity, const QString &name)
|
||||
namespace {
|
||||
QXmlStreamAttributes modifyXmlStreamAttributes(const QXmlStreamAttributes &input, const QStringList &keys,
|
||||
const QStringList values, const QStringList &remove = QStringList())
|
||||
{
|
||||
QDomElement metadataElem = activity.firstChildElement(QLatin1String("meta-data"));
|
||||
while (!metadataElem.isNull()) {
|
||||
if (metadataElem.attribute(QLatin1String("android:name")) == QLatin1String("android.app.lib_name")) {
|
||||
metadataElem.setAttribute(QLatin1String("android:value"), name);
|
||||
return true;
|
||||
}
|
||||
metadataElem = metadataElem.nextSiblingElement(QLatin1String("meta-data"));
|
||||
Q_ASSERT(keys.size() == values.size());
|
||||
QXmlStreamAttributes result;
|
||||
result.reserve(input.size());
|
||||
foreach (const QXmlStreamAttribute &attribute, input) {
|
||||
const QString &name = attribute.qualifiedName().toString();
|
||||
if (remove.contains(name))
|
||||
continue;
|
||||
int index = keys.indexOf(name);
|
||||
if (index == -1)
|
||||
result.push_back(attribute);
|
||||
else
|
||||
result.push_back(QXmlStreamAttribute(name,
|
||||
values.at(index)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
if (!result.hasAttribute(keys.at(i)))
|
||||
result.push_back(QXmlStreamAttribute(keys.at(i), values.at(i)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // end namespace
|
||||
|
||||
void AndroidManifestEditorWidget::parseManifest(QXmlStreamReader &reader, QXmlStreamWriter &writer)
|
||||
{
|
||||
Q_ASSERT(reader.isStartElement());
|
||||
writer.writeStartElement(reader.name().toString());
|
||||
|
||||
QXmlStreamAttributes attributes = reader.attributes();
|
||||
QStringList keys = QStringList()
|
||||
<< QLatin1String("package")
|
||||
<< QLatin1String("android:versionCode")
|
||||
<< QLatin1String("android:versionName");
|
||||
QStringList values = QStringList()
|
||||
<< m_packageNameLineEdit->text()
|
||||
<< QString::number(m_versionCode->value())
|
||||
<< m_versionNameLinedit->text();
|
||||
|
||||
QXmlStreamAttributes result = modifyXmlStreamAttributes(attributes, keys, values);
|
||||
writer.writeAttributes(result);
|
||||
|
||||
QSet<QString> permissions = m_permissionsModel->permissions().toSet();
|
||||
|
||||
bool foundUsesSdk = false;
|
||||
reader.readNext();
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.name() == QLatin1String("application")) {
|
||||
parseApplication(reader, writer);
|
||||
} else if (reader.name() == QLatin1String("uses-sdk")) {
|
||||
parseUsesSdk(reader, writer);
|
||||
foundUsesSdk = true;
|
||||
} else if (reader.name() == QLatin1String("uses-permission")) {
|
||||
permissions.remove(parseUsesPermission(reader, writer, permissions));
|
||||
} else if (reader.isEndElement()) {
|
||||
if (!foundUsesSdk) {
|
||||
int minimumSdk = extractVersion(m_androidMinSdkVersion->currentText());
|
||||
int targetSdk = extractVersion(m_androidTargetSdkVersion->currentText());
|
||||
if (minimumSdk == 0 && targetSdk == 0) {
|
||||
// and doesn't need to exist
|
||||
} else {
|
||||
writer.writeEmptyElement(QLatin1String("uses-sdk"));
|
||||
if (minimumSdk != 0)
|
||||
writer.writeAttribute(QLatin1String("android:minSdkVersion"),
|
||||
QString::number(minimumSdk));
|
||||
if (targetSdk != 0)
|
||||
writer.writeAttribute(QLatin1String("android:targetSdkVersion"),
|
||||
QString::number(targetSdk));
|
||||
}
|
||||
}
|
||||
if (!permissions.isEmpty()) {
|
||||
foreach (const QString &permission, permissions) {
|
||||
writer.writeEmptyElement(QLatin1String("uses-permission"));
|
||||
writer.writeAttribute(QLatin1String("android:name"), permission);
|
||||
}
|
||||
}
|
||||
|
||||
writer.writeCurrentToken(reader);
|
||||
return;
|
||||
} else if (reader.isStartElement()) {
|
||||
parseUnknownElement(reader, writer);
|
||||
} else {
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
reader.readNext();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidManifestEditorWidget::parseApplication(QXmlStreamReader &reader, QXmlStreamWriter &writer)
|
||||
{
|
||||
Q_ASSERT(reader.isStartElement());
|
||||
writer.writeStartElement(reader.name().toString());
|
||||
|
||||
QXmlStreamAttributes attributes = reader.attributes();
|
||||
QStringList keys;
|
||||
QStringList values;
|
||||
if (!m_appNameInStringsXml) {
|
||||
keys << QLatin1String("android:label");
|
||||
values << m_appNameLineEdit->text();
|
||||
}
|
||||
bool ensureIconAttribute = !m_lIconPath.isEmpty()
|
||||
|| !m_mIconPath.isEmpty()
|
||||
|| !m_hIconPath.isEmpty();
|
||||
if (ensureIconAttribute) {
|
||||
keys << QLatin1String("android:icon");
|
||||
values << QLatin1String("@drawable/icon");
|
||||
}
|
||||
|
||||
QXmlStreamAttributes result = modifyXmlStreamAttributes(attributes, keys, values);
|
||||
writer.writeAttributes(result);
|
||||
|
||||
reader.readNext();
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.isEndElement()) {
|
||||
writer.writeCurrentToken(reader);
|
||||
return;
|
||||
} else if (reader.isStartElement()) {
|
||||
if (reader.name() == QLatin1String("activity"))
|
||||
parseActivity(reader, writer);
|
||||
else
|
||||
parseUnknownElement(reader, writer);
|
||||
} else {
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
|
||||
reader.readNext();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidManifestEditorWidget::parseActivity(QXmlStreamReader &reader, QXmlStreamWriter &writer)
|
||||
{
|
||||
Q_ASSERT(reader.isStartElement());
|
||||
writer.writeCurrentToken(reader);
|
||||
reader.readNext();
|
||||
|
||||
bool found = false;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.isEndElement()) {
|
||||
if (!found) {
|
||||
writer.writeEmptyElement(QLatin1String("meta-data"));
|
||||
writer.writeAttribute(QLatin1String("android:name"),
|
||||
QLatin1String("android.app.lib_name"));
|
||||
writer.writeAttribute(QLatin1String("android:value"),
|
||||
m_targetLineEdit->currentText());
|
||||
}
|
||||
writer.writeCurrentToken(reader);
|
||||
return;
|
||||
} else if (reader.isStartElement()) {
|
||||
if (reader.name() == QLatin1String("meta-data"))
|
||||
found = parseMetaData(reader, writer) || found; // ORDER MATTERS
|
||||
else
|
||||
parseUnknownElement(reader, writer);
|
||||
} else {
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
reader.readNext();
|
||||
}
|
||||
}
|
||||
|
||||
bool AndroidManifestEditorWidget::parseMetaData(QXmlStreamReader &reader, QXmlStreamWriter &writer)
|
||||
{
|
||||
Q_ASSERT(reader.isStartElement());
|
||||
|
||||
bool found = false;
|
||||
QXmlStreamAttributes attributes = reader.attributes();
|
||||
QXmlStreamAttributes result;
|
||||
|
||||
if (attributes.value(QLatin1String("android:name")) == QLatin1String("android.app.lib_name")) {
|
||||
QStringList keys = QStringList() << QLatin1String("android:value");
|
||||
QStringList values = QStringList() << m_targetLineEdit->currentText();
|
||||
result = modifyXmlStreamAttributes(attributes, keys, values);
|
||||
found = true;
|
||||
} else {
|
||||
result = attributes;
|
||||
}
|
||||
|
||||
writer.writeStartElement(QLatin1String("meta-data"));
|
||||
writer.writeAttributes(result);
|
||||
|
||||
reader.readNext();
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.isEndElement()) {
|
||||
writer.writeCurrentToken(reader);
|
||||
return found;
|
||||
} else if (reader.isStartElement()) {
|
||||
parseUnknownElement(reader, writer);
|
||||
} else {
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
reader.readNext();
|
||||
}
|
||||
return found; // should never be reached
|
||||
}
|
||||
|
||||
void AndroidManifestEditorWidget::parseUsesSdk(QXmlStreamReader &reader, QXmlStreamWriter & writer)
|
||||
{
|
||||
int minimumSdk = extractVersion(m_androidMinSdkVersion->currentText());
|
||||
int targetSdk = extractVersion(m_androidTargetSdkVersion->currentText());
|
||||
|
||||
QStringList keys;
|
||||
QStringList values;
|
||||
QStringList remove;
|
||||
if (minimumSdk == 0) {
|
||||
remove << QLatin1String("android:minSdkVersion");
|
||||
} else {
|
||||
keys << QLatin1String("android:minSdkVersion");
|
||||
values << QString::number(minimumSdk);
|
||||
}
|
||||
if (targetSdk == 0) {
|
||||
remove << QLatin1String("android:targetSdkVersion");
|
||||
} else {
|
||||
keys << QLatin1String("android:targetSdkVersion");
|
||||
values << QString::number(targetSdk);
|
||||
}
|
||||
|
||||
QXmlStreamAttributes result = modifyXmlStreamAttributes(reader.attributes(),
|
||||
keys, values, remove);
|
||||
bool removeUseSdk = result.isEmpty();
|
||||
if (!removeUseSdk) {
|
||||
writer.writeStartElement(reader.name().toString());
|
||||
writer.writeAttributes(result);
|
||||
}
|
||||
|
||||
reader.readNext();
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.isEndElement()) {
|
||||
if (!removeUseSdk)
|
||||
writer.writeCurrentToken(reader);
|
||||
return;
|
||||
} else {
|
||||
if (removeUseSdk) {
|
||||
removeUseSdk = false;
|
||||
writer.writeStartElement(QLatin1String("uses-sdk"));
|
||||
}
|
||||
|
||||
if (reader.isStartElement())
|
||||
parseUnknownElement(reader, writer);
|
||||
else
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
reader.readNext();
|
||||
}
|
||||
}
|
||||
|
||||
QString AndroidManifestEditorWidget::parseUsesPermission(QXmlStreamReader &reader, QXmlStreamWriter &writer, const QSet<QString> permissions)
|
||||
{
|
||||
Q_ASSERT(reader.isStartElement());
|
||||
|
||||
|
||||
QString permissionName = reader.attributes().value(QLatin1String("android:name")).toString();
|
||||
bool writePermission = permissions.contains(permissionName);
|
||||
if (writePermission)
|
||||
writer.writeCurrentToken(reader);
|
||||
reader.readNext();
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.isEndElement()) {
|
||||
if (writePermission)
|
||||
writer.writeCurrentToken(reader);
|
||||
return permissionName;
|
||||
} else if (reader.isStartElement()) {
|
||||
parseUnknownElement(reader, writer);
|
||||
} else {
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
reader.readNext();
|
||||
}
|
||||
return permissionName; // should not be reached
|
||||
}
|
||||
|
||||
void AndroidManifestEditorWidget::parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer)
|
||||
{
|
||||
Q_ASSERT(reader.isStartElement());
|
||||
writer.writeCurrentToken(reader);
|
||||
reader.readNext();
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.isEndElement()) {
|
||||
writer.writeCurrentToken(reader);
|
||||
return;
|
||||
} else if (reader.isStartElement()) {
|
||||
parseUnknownElement(reader, writer);
|
||||
} else {
|
||||
writer.writeCurrentToken(reader);
|
||||
}
|
||||
reader.readNext();
|
||||
}
|
||||
QDomElement elem = document.createElement(QLatin1String("meta-data"));
|
||||
elem.setAttribute(QLatin1String("android:name"), QLatin1String("android.app.lib_name"));
|
||||
elem.setAttribute(QLatin1String("android:value"), name);
|
||||
activity.appendChild(elem);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString AndroidManifestEditorWidget::iconPath(const QString &baseDir, IconDPI dpi)
|
||||
|
Reference in New Issue
Block a user