202 lines
6.1 KiB
C++
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 ¶graphs,
|
|
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;
|
|
}
|