mumble-voip_mumble/src/mumble/XMLTools.cpp

202 lines
6.1 KiB
C++

// Copyright 2017-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "XMLTools.h"
#include <QStringList>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
void XMLTools::recurseParse(QXmlStreamReader &reader, QXmlStreamWriter &writer, int &paragraphs,
const QMap< QString, QString > &opstyle, const int close, bool ignore) {
while (!reader.atEnd()) {
QXmlStreamReader::TokenType tt = reader.readNext();
QXmlStreamAttributes a = reader.attributes();
QMap< QString, QString > style;
QMap< QString, QString > pstyle = opstyle;
QStringRef styleref = a.value(QLatin1String("style"));
if (!styleref.isNull()) {
QString stylestring = styleref.toString();
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList styles = stylestring.split(QLatin1String(";"), Qt::SkipEmptyParts);
#else
// Qt 5.14 introduced the Qt::SplitBehavior flags deprecating the QString fields
QStringList styles = stylestring.split(QLatin1String(";"), QString::SkipEmptyParts);
#endif
foreach (QString s, styles) {
s = s.simplified();
int idx = s.indexOf(QLatin1Char(':'));
QString key = (idx > 0) ? s.left(idx).simplified() : s;
QString val = (idx > 0) ? s.mid(idx + 1).simplified() : QString();
if (!pstyle.contains(key) || (pstyle.value(key) != val)) {
style.insert(key, val);
pstyle.insert(key, val);
}
}
}
switch (tt) {
case QXmlStreamReader::StartElement: {
QString name = reader.name().toString();
int rclose = 1;
if (name == QLatin1String("body")) {
rclose = 0;
ignore = false;
} else if (name == QLatin1String("span")) {
// Substitute style with <b>, <i> and <u>
rclose = 0;
if (style.value(QLatin1String("font-weight")) == QLatin1String("600")) {
writer.writeStartElement(QLatin1String("b"));
rclose++;
style.remove(QLatin1String("font-weight"));
}
if (style.value(QLatin1String("font-style")) == QLatin1String("italic")) {
writer.writeStartElement(QLatin1String("i"));
rclose++;
style.remove(QLatin1String("font-style"));
}
if (style.value(QLatin1String("text-decoration")) == QLatin1String("underline")) {
writer.writeStartElement(QLatin1String("u"));
rclose++;
style.remove(QLatin1String("text-decoration"));
}
if (!style.isEmpty()) {
rclose++;
writer.writeStartElement(name);
QStringList qsl;
QMap< QString, QString >::const_iterator i;
for (i = style.constBegin(); i != style.constEnd(); ++i) {
if (!i.value().isEmpty())
qsl << QString::fromLatin1("%1:%2").arg(i.key(), i.value());
else
qsl << i.key();
}
writer.writeAttribute(QLatin1String("style"), qsl.join(QLatin1String("; ")));
}
} else if (name == QLatin1String("p")) {
paragraphs++;
if (paragraphs == 1) {
// Ignore first paragraph. If it is styled empty drop its contents (e.g. <br />) too.
if (style.value(QLatin1String("-qt-paragraph-type")) == QLatin1String("empty")) {
reader.skipCurrentElement();
continue;
}
rclose = 0;
} else {
rclose = 1;
writer.writeStartElement(name);
if (!style.isEmpty()) {
QStringList qsl;
QMap< QString, QString >::const_iterator i;
for (i = style.constBegin(); i != style.constEnd(); ++i) {
if (!i.value().isEmpty())
qsl << QString::fromLatin1("%1:%2").arg(i.key(), i.value());
else
qsl << i.key();
}
writer.writeAttribute(QLatin1String("style"), qsl.join(QLatin1String("; ")));
}
}
} else if (name == QLatin1String("a")) {
// Set pstyle to include implicit blue underline.
rclose = 1;
writer.writeCurrentToken(reader);
pstyle.insert(QLatin1String("text-decoration"), QLatin1String("underline"));
pstyle.insert(QLatin1String("color"), QLatin1String("#0000ff"));
} else if (!ignore) {
rclose = 1;
writer.writeCurrentToken(reader);
}
recurseParse(reader, writer, paragraphs, pstyle, rclose, ignore);
break;
}
case QXmlStreamReader::EndElement:
if (!ignore)
for (int i = 0; i < close; ++i)
writer.writeEndElement();
return;
case QXmlStreamReader::Characters:
if (!ignore)
writer.writeCharacters(reader.text().toString());
break;
default:
break;
}
}
}
bool XMLTools::unduplicateTags(QXmlStreamReader &reader, QXmlStreamWriter &writer) {
bool changed = false;
bool needclose = false;
QStringList qslConcat;
qslConcat << QLatin1String("b");
qslConcat << QLatin1String("i");
qslConcat << QLatin1String("u");
qslConcat << QLatin1String("a");
QList< QString > qlNames;
QList< QXmlStreamAttributes > qlAttributes;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType tt = reader.readNext();
QString name = reader.name().toString();
switch (tt) {
case QXmlStreamReader::StartDocument:
case QXmlStreamReader::EndDocument:
break;
case QXmlStreamReader::StartElement: {
QXmlStreamAttributes a = reader.attributes();
if (name == QLatin1String("unduplicate"))
break;
if (needclose) {
needclose = false;
if ((a == qlAttributes.last()) && (name == qlNames.last()) && (qslConcat.contains(name))) {
changed = true;
break;
}
qlNames.takeLast();
qlAttributes.takeLast();
writer.writeEndElement();
}
writer.writeCurrentToken(reader);
qlNames.append(name);
qlAttributes.append(a);
} break;
case QXmlStreamReader::EndElement: {
if (name == QLatin1String("unduplicate"))
break;
if (needclose) {
qlNames.takeLast();
qlAttributes.takeLast();
writer.writeCurrentToken(reader);
}
needclose = true;
} break;
default:
if (needclose) {
writer.writeEndElement();
needclose = false;
}
writer.writeCurrentToken(reader);
}
}
if (needclose)
writer.writeEndElement();
return changed;
}