Files
RedBear-OS/local/recipes/kde/kf6-kxmlgui/source/src/kxmlguiclient.cpp
T
2026-04-14 10:51:06 +01:00

858 lines
28 KiB
C++

/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kxmlguiclient.h"
#include "debug.h"
#include "kactioncollection.h"
#include "kxmlguibuilder.h"
#include "kxmlguifactory.h"
#include "kxmlguiversionhandler_p.h"
#include <QAction>
#include <QCoreApplication>
#include <QDir>
#include <QDomDocument>
#include <QFile>
#include <QPointer>
#include <QStandardPaths>
#include <KAuthorized>
#include <KLocalizedString>
#include <cassert>
class KXMLGUIClientPrivate
{
public:
KXMLGUIClientPrivate()
: m_componentName(QCoreApplication::applicationName())
, m_textTagNames({QStringLiteral("text"), QStringLiteral("Text"), QStringLiteral("title")})
{
}
~KXMLGUIClientPrivate()
{
}
bool mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection);
bool isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const;
QDomElement findMatchingElement(const QDomElement &base, const QDomElement &additive);
QString m_componentName;
QDomDocument m_doc;
KActionCollection *m_actionCollection = nullptr;
QDomDocument m_buildDocument;
QPointer<KXMLGUIFactory> m_factory;
KXMLGUIClient *m_parent = nullptr;
// QPtrList<KXMLGUIClient> m_supers;
QList<KXMLGUIClient *> m_children;
KXMLGUIBuilder *m_builder = nullptr;
QString m_xmlFile;
QString m_localXMLFile;
const QStringList m_textTagNames;
// Actions to enable/disable on a state change
QMap<QString, KXMLGUIClient::StateChange> m_actionsStateMap;
};
KXMLGUIClient::KXMLGUIClient()
: d(new KXMLGUIClientPrivate)
{
}
KXMLGUIClient::KXMLGUIClient(KXMLGUIClient *parent)
: d(new KXMLGUIClientPrivate)
{
Q_INIT_RESOURCE(kxmlgui);
parent->insertChildClient(this);
}
KXMLGUIClient::~KXMLGUIClient()
{
if (d->m_parent) {
d->m_parent->removeChildClient(this);
}
if (d->m_factory) {
qCWarning(DEBUG_KXMLGUI)
<< this << "deleted without having been removed from the factory first. This will leak standalone popupmenus and could lead to crashes.";
d->m_factory->forgetClient(this);
}
for (KXMLGUIClient *client : std::as_const(d->m_children)) {
if (d->m_factory) {
d->m_factory->forgetClient(client);
}
assert(client->d->m_parent == this);
client->d->m_parent = nullptr;
}
delete d->m_actionCollection;
}
QAction *KXMLGUIClient::action(const QString &name) const
{
QAction *act = actionCollection()->action(name);
if (!act) {
for (KXMLGUIClient *client : std::as_const(d->m_children)) {
act = client->actionCollection()->action(name);
if (act) {
break;
}
}
}
return act;
}
KActionCollection *KXMLGUIClient::actionCollection() const
{
if (!d->m_actionCollection) {
d->m_actionCollection = new KActionCollection(this);
d->m_actionCollection->setObjectName(QStringLiteral("KXMLGUIClient-KActionCollection"));
}
return d->m_actionCollection;
}
QAction *KXMLGUIClient::action(const QDomElement &element) const
{
return actionCollection()->action(element.attribute(QStringLiteral("name")));
}
QString KXMLGUIClient::componentName() const
{
return d->m_componentName;
}
QDomDocument KXMLGUIClient::domDocument() const
{
return d->m_doc;
}
QString KXMLGUIClient::xmlFile() const
{
return d->m_xmlFile;
}
QString KXMLGUIClient::localXMLFile() const
{
if (!d->m_localXMLFile.isEmpty()) {
return d->m_localXMLFile;
}
if (!QDir::isRelativePath(d->m_xmlFile)) {
return QString(); // can't save anything here
}
if (d->m_xmlFile.isEmpty()) { // setXMLFile not called at all, can't save. Use case: ToolBarHandler
return QString();
}
return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/%1/%2").arg(componentName(), d->m_xmlFile);
}
void KXMLGUIClient::reloadXML()
{
// TODO: this method can't be used for the KXmlGuiWindow, since it doesn't merge in ui_standards.rc!
// -> KDE5: load ui_standards_rc in setXMLFile using a flag, and remember that flag?
// and then KEditToolBar can use reloadXML.
QString file(xmlFile());
if (!file.isEmpty()) {
setXMLFile(file);
}
}
void KXMLGUIClient::setComponentName(const QString &componentName, const QString &componentDisplayName)
{
d->m_componentName = componentName;
actionCollection()->setComponentName(componentName);
actionCollection()->setComponentDisplayName(componentDisplayName);
if (d->m_builder) {
d->m_builder->setBuilderClient(this);
}
}
QString KXMLGUIClient::standardsXmlFileLocation()
{
if (QStandardPaths::isTestModeEnabled()) {
return QStringLiteral(":/kxmlgui5/ui_standards.rc");
}
QString file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("kxmlgui5/ui_standards.rc"));
if (file.isEmpty()) {
// fallback to resource, to allow to use the rc file compiled into this framework, must exist!
file = QStringLiteral(":/kxmlgui5/ui_standards.rc");
Q_ASSERT(QFile::exists(file));
}
return file;
}
void KXMLGUIClient::loadStandardsXmlFile()
{
setXML(KXMLGUIFactory::readConfigFile(standardsXmlFileLocation()));
}
void KXMLGUIClient::setXMLFile(const QString &_file, bool merge, bool setXMLDoc)
{
// store our xml file name
if (!_file.isNull()) {
d->m_xmlFile = _file;
}
if (!setXMLDoc) {
return;
}
QString file = _file;
QStringList allFiles;
if (!QDir::isRelativePath(file)) {
allFiles.append(file);
} else {
const QString filter = componentName() + QLatin1Char('/') + _file;
// files on filesystem
allFiles << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kxmlgui5/") + filter);
// built-in resource file
const QString qrcFile(QLatin1String(":/kxmlgui5/") + filter);
if (QFile::exists(qrcFile)) {
allFiles << qrcFile;
}
}
if (allFiles.isEmpty() && !_file.isEmpty()) {
// if a non-empty file gets passed and we can't find it,
// inform the developer using some debug output
qCWarning(DEBUG_KXMLGUI) << "cannot find .rc file" << _file << "for component" << componentName();
}
// make sure to merge the settings from any file specified by setLocalXMLFile()
if (!d->m_localXMLFile.isEmpty() && !file.endsWith(QLatin1String("ui_standards.rc"))) {
const bool exists = QDir::isRelativePath(d->m_localXMLFile) || QFile::exists(d->m_localXMLFile);
if (exists && !allFiles.contains(d->m_localXMLFile)) {
allFiles.prepend(d->m_localXMLFile);
}
}
QString doc;
if (!allFiles.isEmpty()) {
file = findMostRecentXMLFile(allFiles, doc);
}
// Always call setXML, even on error, so that we don't keep all ui_standards.rc menus.
setXML(doc, merge);
}
void KXMLGUIClient::setLocalXMLFile(const QString &file)
{
d->m_localXMLFile = file;
}
void KXMLGUIClient::replaceXMLFile(const QString &xmlfile, const QString &localxmlfile, bool merge)
{
if (!QDir::isAbsolutePath(xmlfile)) {
qCWarning(DEBUG_KXMLGUI) << "xml file" << xmlfile << "is not an absolute path";
}
setLocalXMLFile(localxmlfile);
setXMLFile(xmlfile, merge);
}
// The top document element may have translation domain attribute set,
// or the translation domain may be implicitly the application domain.
// This domain must be used to fetch translations for all text elements
// in the document that do not have their own domain attribute.
// In order to preserve this semantics through document mergings,
// the top or application domain must be propagated to all text elements
// lacking their own domain attribute.
static void propagateTranslationDomain(QDomDocument &doc, const QStringList &tagNames)
{
const QLatin1String attrDomain("translationDomain");
QDomElement base = doc.documentElement();
QString domain = base.attribute(attrDomain);
if (domain.isEmpty()) {
domain = QString::fromUtf8(KLocalizedString::applicationDomain());
if (domain.isEmpty()) {
return;
}
}
for (const QString &tagName : tagNames) {
QDomNodeList textNodes = base.elementsByTagName(tagName);
for (int i = 0; i < textNodes.length(); ++i) {
QDomElement e = textNodes.item(i).toElement();
QString localDomain = e.attribute(attrDomain);
if (localDomain.isEmpty()) {
e.setAttribute(attrDomain, domain);
}
}
}
}
void KXMLGUIClient::setXML(const QString &document, bool merge)
{
QDomDocument doc;
// QDomDocument raises a parse error on empty document, but we accept no app-specific document,
// in which case you only get ui_standards.rc layout.
if (!document.isEmpty()) {
const QDomDocument::ParseResult result = doc.setContent(document);
if (!result) {
qCCritical(DEBUG_KXMLGUI) << "Error parsing XML document:" << result.errorMessage << "at line" << result.errorLine << "column"
<< result.errorColumn;
#ifdef NDEBUG
setDOMDocument(QDomDocument(), merge); // otherwise empty menus from ui_standards.rc stay around
#else
abort();
#endif
return;
}
}
propagateTranslationDomain(doc, d->m_textTagNames);
setDOMDocument(doc, merge);
}
void KXMLGUIClient::setDOMDocument(const QDomDocument &document, bool merge)
{
if (merge && !d->m_doc.isNull()) {
QDomElement base = d->m_doc.documentElement();
QDomElement e = document.documentElement();
// merge our original (global) xml with our new one
d->mergeXML(base, e, actionCollection());
// reassign our pointer as mergeXML might have done something
// strange to it
base = d->m_doc.documentElement();
// qCDebug(DEBUG_KXMLGUI) << "Result of xmlgui merging:" << d->m_doc.toString();
// we want some sort of failsafe.. just in case
if (base.isNull()) {
d->m_doc = document;
}
} else {
d->m_doc = document;
}
setXMLGUIBuildDocument(QDomDocument());
}
// if (equals(a,b)) is more readable than if (a.compare(b, Qt::CaseInsensitive)==0)
static inline bool equalstr(const QString &a, const QString &b)
{
return a.compare(b, Qt::CaseInsensitive) == 0;
}
static inline bool equalstr(const QString &a, QLatin1String b)
{
return a.compare(b, Qt::CaseInsensitive) == 0;
}
bool KXMLGUIClientPrivate::mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection)
{
const QLatin1String tagAction("Action");
const QLatin1String tagMerge("Merge");
const QLatin1String tagSeparator("Separator");
const QLatin1String tagMergeLocal("MergeLocal");
const QLatin1String tagText("text");
const QLatin1String attrAppend("append");
const QString attrName(QStringLiteral("name"));
const QString attrWeakSeparator(QStringLiteral("weakSeparator"));
const QString attrAlreadyVisited(QStringLiteral("alreadyVisited"));
const QString attrNoMerge(QStringLiteral("noMerge"));
const QLatin1String attrOne("1");
// there is a possibility that we don't want to merge in the
// additive.. rather, we might want to *replace* the base with the
// additive. this can be for any container.. either at a file wide
// level or a simple container level. we look for the 'noMerge'
// tag, in any event and just replace the old with the new
if (additive.attribute(attrNoMerge) == attrOne) { // ### use toInt() instead? (Simon)
base.parentNode().replaceChild(additive, base);
return true;
} else {
// Merge attributes
{
const QDomNamedNodeMap attribs = additive.attributes();
const int attribcount = attribs.count();
for (int i = 0; i < attribcount; ++i) {
const QDomNode node = attribs.item(i);
base.setAttribute(node.nodeName(), node.nodeValue());
}
}
// iterate over all elements in the container (of the global DOM tree)
QDomNode n = base.firstChild();
while (!n.isNull()) {
QDomElement e = n.toElement();
n = n.nextSibling(); // Advance now so that we can safely delete e
if (e.isNull()) {
continue;
}
const QString tag = e.tagName();
// if there's an action tag in the global tree and the action is
// not implemented, then we remove the element
if (equalstr(tag, tagAction)) {
const QString name = e.attribute(attrName);
if (!actionCollection->action(name) || !KAuthorized::authorizeAction(name)) {
// remove this child as we aren't using it
base.removeChild(e);
continue;
}
}
// if there's a separator defined in the global tree, then add an
// attribute, specifying that this is a "weak" separator
else if (equalstr(tag, tagSeparator)) {
e.setAttribute(attrWeakSeparator, uint(1));
// okay, hack time. if the last item was a weak separator OR
// this is the first item in a container, then we nuke the
// current one
QDomElement prev = e.previousSibling().toElement();
if (prev.isNull() //
|| (equalstr(prev.tagName(), tagSeparator) && !prev.attribute(attrWeakSeparator).isNull()) //
|| (equalstr(prev.tagName(), tagText))) {
// the previous element was a weak separator or didn't exist
base.removeChild(e);
continue;
}
}
// the MergeLocal tag lets us specify where non-standard elements
// of the local tree shall be merged in. After inserting the
// elements we delete this element
else if (equalstr(tag, tagMergeLocal)) {
QDomNode it = additive.firstChild();
while (!it.isNull()) {
QDomElement newChild = it.toElement();
it = it.nextSibling();
if (newChild.isNull()) {
continue;
}
if (equalstr(newChild.tagName(), tagText)) {
continue;
}
if (newChild.attribute(attrAlreadyVisited) == attrOne) {
continue;
}
QString itAppend(newChild.attribute(attrAppend));
QString elemName(e.attribute(attrName));
if ((itAppend.isNull() && elemName.isEmpty()) || (itAppend == elemName)) {
// first, see if this new element matches a standard one in
// the global file. if it does, then we skip it as it will
// be merged in, later
QDomElement matchingElement = findMatchingElement(newChild, base);
if (matchingElement.isNull() || equalstr(newChild.tagName(), tagSeparator)) {
base.insertBefore(newChild, e);
}
}
}
base.removeChild(e);
continue;
}
else if (equalstr(tag, tagText)) {
continue;
} else if (equalstr(tag, tagMerge)) {
continue;
}
// in this last case we check for a separator tag and, if not, we
// can be sure that it is a container --> proceed with child nodes
// recursively and delete the just proceeded container item in
// case it is empty (if the recursive call returns true)
else {
QDomElement matchingElement = findMatchingElement(e, additive);
if (!matchingElement.isNull()) {
matchingElement.setAttribute(attrAlreadyVisited, uint(1));
if (mergeXML(e, matchingElement, actionCollection)) {
base.removeChild(e);
additive.removeChild(matchingElement); // make sure we don't append it below
continue;
}
continue;
} else {
// this is an important case here! We reach this point if the
// "local" tree does not contain a container definition for
// this container. However we have to call mergeXML recursively
// and make it check if there are actions implemented for this
// container. *If* none, then we can remove this container now
QDomElement dummy;
if (mergeXML(e, dummy, actionCollection)) {
base.removeChild(e);
}
continue;
}
}
}
// here we append all child elements which were not inserted
// previously via the LocalMerge tag
n = additive.firstChild();
while (!n.isNull()) {
QDomElement e = n.toElement();
n = n.nextSibling(); // Advance now so that we can safely delete e
if (e.isNull()) {
continue;
}
QDomElement matchingElement = findMatchingElement(e, base);
if (matchingElement.isNull()) {
base.appendChild(e);
}
}
// do one quick check to make sure that the last element was not
// a weak separator
QDomElement last = base.lastChild().toElement();
if (equalstr(last.tagName(), tagSeparator) && (!last.attribute(attrWeakSeparator).isNull())) {
base.removeChild(last);
}
}
return isEmptyContainer(base, actionCollection);
}
bool KXMLGUIClientPrivate::isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const
{
// now we check if we are empty (in which case we return "true", to
// indicate the caller that it can delete "us" (the base element
// argument of "this" call)
QDomNode n = base.firstChild();
while (!n.isNull()) {
const QDomElement e = n.toElement();
n = n.nextSibling(); // Advance now so that we can safely delete e
if (e.isNull()) {
continue;
}
const QString tag = e.tagName();
if (equalstr(tag, QLatin1String("Action"))) {
// if base contains an implemented action, then we must not get
// deleted (note that the actionCollection contains both,
// "global" and "local" actions)
if (actionCollection->action(e.attribute(QStringLiteral("name")))) {
return false;
}
} else if (equalstr(tag, QLatin1String("Separator"))) {
// if we have a separator which has *not* the weak attribute
// set, then it must be owned by the "local" tree in which case
// we must not get deleted either
const QString weakAttr = e.attribute(QStringLiteral("weakSeparator"));
if (weakAttr.isEmpty() || weakAttr.toInt() != 1) {
return false;
}
}
else if (equalstr(tag, QLatin1String("merge"))) {
continue;
}
// a text tag is NOT enough to spare this container
else if (equalstr(tag, QLatin1String("text"))) {
continue;
}
// what's left are non-empty containers! *don't* delete us in this
// case (at this position we can be *sure* that the container is
// *not* empty, as the recursive call for it was in the first loop
// which deleted the element in case the call returned "true"
else {
return false;
}
}
return true; // I'm empty, please delete me.
}
QDomElement KXMLGUIClientPrivate::findMatchingElement(const QDomElement &base, const QDomElement &additive)
{
const QString idAttribute(base.tagName() == QLatin1String("ActionProperties") ? QStringLiteral("scheme") : QStringLiteral("name"));
QDomNode n = additive.firstChild();
while (!n.isNull()) {
QDomElement e = n.toElement();
n = n.nextSibling(); // Advance now so that we can safely delete e -- TODO we don't, so simplify this
if (e.isNull()) {
continue;
}
const QString tag = e.tagName();
// skip all action and merge tags as we will never use them
if (equalstr(tag, QLatin1String("Action")) //
|| equalstr(tag, QLatin1String("MergeLocal"))) {
continue;
}
// now see if our tags are equivalent
if (equalstr(tag, base.tagName()) //
&& e.attribute(idAttribute) == base.attribute(idAttribute)) {
return e;
}
}
// nope, return a (now) null element
return QDomElement();
}
void KXMLGUIClient::setXMLGUIBuildDocument(const QDomDocument &doc)
{
d->m_buildDocument = doc;
}
QDomDocument KXMLGUIClient::xmlguiBuildDocument() const
{
return d->m_buildDocument;
}
void KXMLGUIClient::setFactory(KXMLGUIFactory *factory)
{
d->m_factory = factory;
}
KXMLGUIFactory *KXMLGUIClient::factory() const
{
return d->m_factory;
}
KXMLGUIClient *KXMLGUIClient::parentClient() const
{
return d->m_parent;
}
void KXMLGUIClient::insertChildClient(KXMLGUIClient *child)
{
if (child->d->m_parent) {
child->d->m_parent->removeChildClient(child);
}
d->m_children.append(child);
child->d->m_parent = this;
}
void KXMLGUIClient::removeChildClient(KXMLGUIClient *child)
{
assert(d->m_children.contains(child));
d->m_children.removeAll(child);
child->d->m_parent = nullptr;
}
/*bool KXMLGUIClient::addSuperClient( KXMLGUIClient *super )
{
if ( d->m_supers.contains( super ) )
return false;
d->m_supers.append( super );
return true;
}*/
QList<KXMLGUIClient *> KXMLGUIClient::childClients()
{
return d->m_children;
}
void KXMLGUIClient::setClientBuilder(KXMLGUIBuilder *builder)
{
d->m_builder = builder;
}
KXMLGUIBuilder *KXMLGUIClient::clientBuilder() const
{
return d->m_builder;
}
void KXMLGUIClient::plugActionList(const QString &name, const QList<QAction *> &actionList)
{
if (!d->m_factory) {
return;
}
d->m_factory->plugActionList(this, name, actionList);
}
void KXMLGUIClient::unplugActionList(const QString &name)
{
if (!d->m_factory) {
return;
}
d->m_factory->unplugActionList(this, name);
}
QString KXMLGUIClient::findMostRecentXMLFile(const QStringList &files, QString &doc)
{
KXmlGuiVersionHandler versionHandler(files);
doc = versionHandler.finalDocument();
return versionHandler.finalFile();
}
void KXMLGUIClient::addStateActionEnabled(const QString &state, const QString &action)
{
StateChange stateChange = getActionsToChangeForState(state);
stateChange.actionsToEnable.append(action);
// qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionEnabled( " << state << ", " << action << ")";
d->m_actionsStateMap.insert(state, stateChange);
}
void KXMLGUIClient::addStateActionDisabled(const QString &state, const QString &action)
{
StateChange stateChange = getActionsToChangeForState(state);
stateChange.actionsToDisable.append(action);
// qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionDisabled( " << state << ", " << action << ")";
d->m_actionsStateMap.insert(state, stateChange);
}
KXMLGUIClient::StateChange KXMLGUIClient::getActionsToChangeForState(const QString &state)
{
return d->m_actionsStateMap[state];
}
void KXMLGUIClient::stateChanged(const QString &newstate, KXMLGUIClient::ReverseStateChange reverse)
{
const StateChange stateChange = getActionsToChangeForState(newstate);
bool setTrue = (reverse == StateNoReverse);
bool setFalse = !setTrue;
// Enable actions which need to be enabled...
//
for (const auto &actionId : stateChange.actionsToEnable) {
QAction *action = actionCollection()->action(actionId);
if (action) {
action->setEnabled(setTrue);
}
}
// and disable actions which need to be disabled...
//
for (const auto &actionId : stateChange.actionsToDisable) {
QAction *action = actionCollection()->action(actionId);
if (action) {
action->setEnabled(setFalse);
}
}
}
void KXMLGUIClient::beginXMLPlug(QWidget *w)
{
actionCollection()->addAssociatedWidget(w);
for (KXMLGUIClient *client : std::as_const(d->m_children)) {
client->beginXMLPlug(w);
}
}
void KXMLGUIClient::endXMLPlug()
{
}
void KXMLGUIClient::prepareXMLUnplug(QWidget *w)
{
actionCollection()->removeAssociatedWidget(w);
for (KXMLGUIClient *client : std::as_const(d->m_children)) {
client->prepareXMLUnplug(w);
}
}
void KXMLGUIClient::virtual_hook(int, void *)
{
/*BASE::virtual_hook( id, data );*/
}
QString KXMLGUIClient::findVersionNumber(const QString &xml)
{
enum {
ST_START,
ST_AFTER_OPEN,
ST_AFTER_GUI,
ST_EXPECT_VERSION,
ST_VERSION_NUM,
} state = ST_START;
const int length = xml.length();
for (int pos = 0; pos < length; pos++) {
switch (state) {
case ST_START:
if (xml[pos] == QLatin1Char('<')) {
state = ST_AFTER_OPEN;
}
break;
case ST_AFTER_OPEN: {
// Jump to gui..
const int guipos = xml.indexOf(QLatin1String("gui"), pos, Qt::CaseInsensitive);
if (guipos == -1) {
return QString(); // Reject
}
pos = guipos + 2; // Position at i, so we're moved ahead to the next character by the ++;
state = ST_AFTER_GUI;
break;
}
case ST_AFTER_GUI:
state = ST_EXPECT_VERSION;
break;
case ST_EXPECT_VERSION: {
const int verpos = xml.indexOf(QLatin1String("version"), pos, Qt::CaseInsensitive);
if (verpos == -1) {
return QString(); // Reject
}
pos = verpos + 7; // strlen("version") is 7
while (xml.at(pos).isSpace()) {
++pos;
}
if (xml.at(pos++) != QLatin1Char('=')) {
return QString(); // Reject
}
while (xml.at(pos).isSpace()) {
++pos;
}
state = ST_VERSION_NUM;
break;
}
case ST_VERSION_NUM: {
int endpos;
for (endpos = pos; endpos < length; endpos++) {
const ushort ch = xml[endpos].unicode();
if (ch >= QLatin1Char('0') && ch <= QLatin1Char('9')) {
continue; // Number..
}
if (ch == QLatin1Char('"')) { // End of parameter
break;
} else { // This shouldn't be here..
endpos = length;
}
}
if (endpos != pos && endpos < length) {
const QString matchCandidate = xml.mid(pos, endpos - pos); // Don't include " ".
return matchCandidate;
}
state = ST_EXPECT_VERSION; // Try to match a well-formed version..
break;
} // case..
} // switch
} // for
return QString();
}