|  | /*************************************************************************** | 
|  | ** | 
|  | ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB) | 
|  | ** Contact: https://www.qt.io/licensing/ | 
|  | ** | 
|  | ** This file is part of the utilities of the Qt Toolkit. | 
|  | ** | 
|  | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
|  | ** Commercial License Usage | 
|  | ** Licensees holding valid commercial Qt licenses may use this file in | 
|  | ** accordance with the commercial license agreement provided with the | 
|  | ** Software or, alternatively, in accordance with the terms contained in | 
|  | ** a written agreement between you and The Qt Company. For licensing terms | 
|  | ** and conditions see https://www.qt.io/terms-conditions. For further | 
|  | ** information use the contact form at https://www.qt.io/contact-us. | 
|  | ** | 
|  | ** GNU General Public License Usage | 
|  | ** Alternatively, this file may be used under the terms of the GNU | 
|  | ** General Public License version 3 as published by the Free Software | 
|  | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
|  | ** included in the packaging of this file. Please review the following | 
|  | ** information to ensure the GNU General Public License requirements will | 
|  | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
|  | ** | 
|  | ** $QT_END_LICENSE$ | 
|  | ** | 
|  | ****************************************************************************/ | 
|  |  | 
|  | #include "xmlspecparser.h" | 
|  |  | 
|  | #include <QDebug> | 
|  | #include <QFile> | 
|  | #include <QRegularExpression> | 
|  | #include <QStringList> | 
|  | #include <QTextStream> | 
|  | #include <QXmlStreamReader> | 
|  |  | 
|  | #ifdef SPECPARSER_DEBUG | 
|  | #define qXmlSpecParserDebug qDebug | 
|  | #else | 
|  | #define qXmlSpecParserDebug QT_NO_QDEBUG_MACRO | 
|  | #endif | 
|  |  | 
|  | bool XmlSpecParser::parse() | 
|  | { | 
|  | // Open up a stream on the actual OpenGL function spec file | 
|  | QFile file(specFileName()); | 
|  | if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { | 
|  | qWarning() << "Failed to open spec file:" << specFileName() << "Aborting"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | QXmlStreamReader stream(&file); | 
|  |  | 
|  | // Extract the info that we need | 
|  | parseFunctions(stream); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseParam(QXmlStreamReader &stream, Function &func) | 
|  | { | 
|  | Argument arg; | 
|  | arg.type = QString(); | 
|  |  | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "ptype") { | 
|  | if (stream.readNext() == QXmlStreamReader::Characters) | 
|  | arg.type.append(stream.text().toString()); | 
|  | } | 
|  |  | 
|  | else if (tag == "name") { | 
|  | if (stream.readNext() == QXmlStreamReader::Characters) | 
|  | arg.name = stream.text().toString().trimmed(); | 
|  | } | 
|  | } else if (stream.isCharacters()) { | 
|  | arg.type.append(stream.text().toString()); | 
|  | } else if (stream.isEndElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "param") { | 
|  | // compatibility with old spec | 
|  | QRegularExpression typeRegExp("(const )?(.+)(?<!\\*)((?:(?!\\*$)\\*)*)(\\*)?"); | 
|  |  | 
|  | // remove extra whitespace | 
|  | arg.type = arg.type.trimmed(); | 
|  |  | 
|  | // set default | 
|  | arg.direction = Argument::In; | 
|  | arg.mode = Argument::Value; | 
|  |  | 
|  | QRegularExpressionMatch exp = typeRegExp.match(arg.type); | 
|  | if (exp.hasMatch()) { | 
|  | if (!exp.captured(4).isEmpty()) { | 
|  | arg.mode = Argument::Reference; | 
|  |  | 
|  | if (exp.captured(1).isEmpty()) | 
|  | arg.direction = Argument::Out; | 
|  | } | 
|  |  | 
|  | arg.type = exp.captured(2) + exp.captured(3); | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // remove any excess whitespace | 
|  | arg.type = arg.type.trimmed(); | 
|  | arg.name = arg.name.trimmed(); | 
|  |  | 
|  | // maybe some checks? | 
|  | func.arguments.append(arg); | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseCommand(QXmlStreamReader &stream) | 
|  | { | 
|  | Function func; | 
|  |  | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "proto") { | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  | if (stream.isStartElement() && (stream.name().toString() == "name")) { | 
|  | if (stream.readNext() == QXmlStreamReader::Characters) | 
|  | func.name = stream.text().toString(); | 
|  | } else if (stream.isCharacters()) { | 
|  | func.returnType.append(stream.text().toString()); | 
|  | } else if (stream.isEndElement() && (stream.name().toString() == "proto")) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (tag == "param") | 
|  | parseParam(stream, func); | 
|  | } | 
|  |  | 
|  | else if (stream.isEndElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "command") | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // maybe checks? | 
|  | func.returnType = func.returnType.trimmed(); | 
|  |  | 
|  | // for compatibility with old spec | 
|  | if (func.name.startsWith("gl")) | 
|  | func.name.remove(0, 2); | 
|  |  | 
|  | m_functionList.insert(func.name, func); | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseCommands(QXmlStreamReader &stream) | 
|  | { | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "command") | 
|  | parseCommand(stream); | 
|  | } | 
|  |  | 
|  | else if (stream.isEndElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "commands") | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseRequire(QXmlStreamReader &stream, FunctionList &funcs) | 
|  | { | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "command") { | 
|  | QString func = stream.attributes().value("name").toString(); | 
|  |  | 
|  | // for compatibility with old spec | 
|  | if (func.startsWith("gl")) | 
|  | func.remove(0, 2); | 
|  |  | 
|  | funcs.append(m_functionList[func]); | 
|  | } | 
|  | } else if (stream.isEndElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "require") | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseRemoveCore(QXmlStreamReader &stream) | 
|  | { | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "command") { | 
|  | QString func = stream.attributes().value("name").toString(); | 
|  |  | 
|  | // for compatibility with old spec | 
|  | if (func.startsWith("gl")) | 
|  | func.remove(0, 2); | 
|  |  | 
|  | // go through list of version and mark as incompatible | 
|  | Q_FOREACH (const Version& v, m_versions) { | 
|  | // Combine version and profile for this subset of functions | 
|  | VersionProfile version; | 
|  | version.version = v; | 
|  | version.profile = VersionProfile::CoreProfile; | 
|  |  | 
|  | // Fetch the functions and add to collection for this class | 
|  | Q_FOREACH (const Function& f, m_functions.values(version)) { | 
|  | if (f.name == func) { | 
|  | VersionProfile newVersion; | 
|  | newVersion.version = v; | 
|  | newVersion.profile = VersionProfile::CompatibilityProfile; | 
|  |  | 
|  | m_functions.insert(newVersion, f); | 
|  | m_functions.remove(version, f); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } else if (stream.isEndElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "remove") | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseFeature(QXmlStreamReader &stream) | 
|  | { | 
|  | QRegularExpression versionRegExp("(\\d).(\\d)"); | 
|  | QXmlStreamAttributes attributes = stream.attributes(); | 
|  |  | 
|  | QRegularExpressionMatch match = versionRegExp.match(attributes.value("number").toString()); | 
|  |  | 
|  | if (!match.hasMatch()) { | 
|  | qWarning() << "Malformed version indicator"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (attributes.value("api").toString() != "gl") | 
|  | return; | 
|  |  | 
|  | int majorVersion = match.captured(1).toInt(); | 
|  | int minorVersion = match.captured(2).toInt(); | 
|  |  | 
|  | Version v; | 
|  | VersionProfile core, compat; | 
|  |  | 
|  | v.major = majorVersion; | 
|  | v.minor = minorVersion; | 
|  | core.version = compat.version = v; | 
|  | core.profile = VersionProfile::CoreProfile; | 
|  | compat.profile = VersionProfile::CompatibilityProfile; | 
|  |  | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "require") { | 
|  | FunctionList funcs; | 
|  |  | 
|  | if (stream.attributes().value("profile").toString() == "compatibility") { | 
|  | parseRequire(stream, funcs); | 
|  | Q_FOREACH (const Function& f, funcs) { | 
|  | m_functions.insert(compat, f); | 
|  | } | 
|  | } else { | 
|  | parseRequire(stream, funcs); | 
|  | Q_FOREACH (const Function& f, funcs) { | 
|  | m_functions.insert(core, f); | 
|  | } | 
|  | } | 
|  | } else if (tag == "remove") { | 
|  | if (stream.attributes().value("profile").toString() == "core") | 
|  | parseRemoveCore(stream); | 
|  | } | 
|  | } else if (stream.isEndElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "feature") | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | m_versions.append(v); | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseExtension(QXmlStreamReader &stream) | 
|  | { | 
|  | QXmlStreamAttributes attributes = stream.attributes(); | 
|  | QString name = attributes.value("name").toString(); | 
|  |  | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "require") { | 
|  | if (stream.attributes().value("profile").toString() == "compatibility") { | 
|  | FunctionList funcs; | 
|  | parseRequire(stream, funcs); | 
|  |  | 
|  | Q_FOREACH (const Function& f, funcs) { | 
|  | FunctionProfile fp; | 
|  | fp.function = f; | 
|  | fp.profile = VersionProfile::CompatibilityProfile; | 
|  | m_extensionFunctions.insert(name, fp); | 
|  | } | 
|  | } else { | 
|  | FunctionList funcs; | 
|  | parseRequire(stream, funcs); | 
|  | Q_FOREACH (const Function& f, funcs) { | 
|  | FunctionProfile fp; | 
|  | fp.function = f; | 
|  | fp.profile = VersionProfile::CoreProfile; | 
|  | m_extensionFunctions.insert(name, fp); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | } | 
|  | } else if (stream.isEndElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "extension") | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void XmlSpecParser::parseFunctions(QXmlStreamReader &stream) | 
|  | { | 
|  | while (!stream.isEndDocument()) { | 
|  | stream.readNext(); | 
|  |  | 
|  | if (stream.isStartElement()) { | 
|  | QString tag = stream.name().toString(); | 
|  |  | 
|  | if (tag == "feature") { | 
|  | parseFeature(stream); | 
|  | } else if (tag == "commands") { | 
|  | parseCommands(stream); | 
|  | } else if (tag == "extension") { | 
|  | parseExtension(stream); | 
|  | } | 
|  | } else if (stream.isEndElement()) { | 
|  | stream.readNext(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // hack - add GL_ARB_imaging to every version after 1.2 inclusive | 
|  | Version versionThreshold; | 
|  | versionThreshold.major = 1; | 
|  | versionThreshold.minor = 2; | 
|  | QList<FunctionProfile> funcs = m_extensionFunctions.values("GL_ARB_imaging"); | 
|  |  | 
|  | VersionProfile vp; | 
|  | vp.version = versionThreshold; | 
|  |  | 
|  | Q_FOREACH (const FunctionProfile& fp, funcs) { | 
|  | vp.profile = fp.profile; | 
|  | m_functions.insert(vp, fp.function); | 
|  | } | 
|  |  | 
|  | // now we will prune any duplicates | 
|  | QSet<QString> funcset; | 
|  |  | 
|  | Q_FOREACH (const Version& v, m_versions) { | 
|  | // check compatibility first | 
|  | VersionProfile vp; | 
|  | vp.version = v; | 
|  |  | 
|  | vp.profile = VersionProfile::CompatibilityProfile; | 
|  |  | 
|  | Q_FOREACH (const Function& f, m_functions.values(vp)) { | 
|  | // remove duplicate | 
|  | if (funcset.contains(f.name)) | 
|  | m_functions.remove(vp, f); | 
|  |  | 
|  | funcset.insert(f.name); | 
|  | } | 
|  |  | 
|  | vp.profile = VersionProfile::CoreProfile; | 
|  |  | 
|  | Q_FOREACH (const Function& f, m_functions.values(vp)) { | 
|  |  | 
|  | // remove duplicate | 
|  | if (funcset.contains(f.name)) | 
|  | m_functions.remove(vp, f); | 
|  |  | 
|  | funcset.insert(f.name); | 
|  | } | 
|  | } | 
|  | } |