/*************************************************************************** ** ** 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); } } }