Chris@66: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@66: 
Chris@66: /*
Chris@66:     Sonic Visualiser
Chris@66:     An audio file viewer and annotation editor.
Chris@66:     Centre for Digital Music, Queen Mary, University of London.
Chris@66:     This file copyright 2006 Chris Cannam.
Chris@66:     
Chris@66:     This program is free software; you can redistribute it and/or
Chris@66:     modify it under the terms of the GNU General Public License as
Chris@66:     published by the Free Software Foundation; either version 2 of the
Chris@66:     License, or (at your option) any later version.  See the file
Chris@66:     COPYING included with this distribution for more information.
Chris@66: */
Chris@66: 
Chris@66: #include "PluginXml.h"
Chris@66: 
Chris@66: #include <QRegExp>
Chris@66: #include <QXmlAttributes>
Chris@66: 
Chris@66: #include <QDomDocument>
Chris@66: #include <QDomElement>
Chris@66: #include <QDomNamedNodeMap>
Chris@66: #include <QDomAttr>
Chris@66: 
Chris@314: #include <QTextStream>
Chris@314: 
Chris@475: #include <vamp-hostsdk/PluginBase.h>
Chris@75: #include "RealTimePluginInstance.h"
Chris@66: 
Chris@66: #include <iostream>
Chris@66: 
Chris@66: PluginXml::PluginXml(Vamp::PluginBase *plugin) :
Chris@66:     m_plugin(plugin)
Chris@66: {
Chris@66: }
Chris@66: 
Chris@66: PluginXml::~PluginXml() { }
Chris@66: 
Chris@66: QString
Chris@81: PluginXml::encodeConfigurationChars(QString text)
Chris@81: {
Chris@81:     QString rv(text);
Chris@81:     rv.replace(";", "[[SEMICOLON]]");
Chris@81:     rv.replace("=", "[[EQUALS]]");
Chris@81:     return rv;
Chris@81: }
Chris@81: 
Chris@81: QString
Chris@81: PluginXml::decodeConfigurationChars(QString text)
Chris@81: {
Chris@81:     QString rv(text);
Chris@81:     rv.replace("[[SEMICOLON]]", ";");
Chris@81:     rv.replace("[[EQUALS]]", "=");
Chris@81:     return rv;
Chris@81: }
Chris@81:     
Chris@314: void
Chris@314: PluginXml::toXml(QTextStream &stream,
Chris@314:                  QString indent, QString extraAttributes) const
Chris@66: {
Chris@314:     stream << indent;
Chris@66: 
Chris@314:     stream << QString("<plugin identifier=\"%1\" name=\"%2\" description=\"%3\" maker=\"%4\" version=\"%5\" copyright=\"%6\" %7 ")
Chris@239:         .arg(encodeEntities(QString(m_plugin->getIdentifier().c_str())))
Chris@66:         .arg(encodeEntities(QString(m_plugin->getName().c_str())))
Chris@66:         .arg(encodeEntities(QString(m_plugin->getDescription().c_str())))
Chris@66:         .arg(encodeEntities(QString(m_plugin->getMaker().c_str())))
Chris@66:         .arg(m_plugin->getPluginVersion())
Chris@66:         .arg(encodeEntities(QString(m_plugin->getCopyright().c_str())))
Chris@66:         .arg(extraAttributes);
Chris@66: 
Chris@66:     if (!m_plugin->getPrograms().empty()) {
Chris@314:         stream << QString("program=\"%1\" ")
Chris@66:             .arg(encodeEntities(m_plugin->getCurrentProgram().c_str()));
Chris@66:     }
Chris@66: 
Chris@66:     Vamp::PluginBase::ParameterList parameters =
Chris@66:         m_plugin->getParameterDescriptors();
Chris@66: 
Chris@66:     for (Vamp::PluginBase::ParameterList::const_iterator i = parameters.begin();
Chris@66:          i != parameters.end(); ++i) {
Chris@185: 
Chris@690: //        SVDEBUG << "PluginXml::toXml: parameter name \""
Chris@185: //                  << i->name.c_str() << "\" has value "
Chris@687: //                  << m_plugin->getParameter(i->name) << endl;
Chris@185: 
Chris@314:         stream << QString("param-%1=\"%2\" ")
Chris@239:             .arg(stripInvalidParameterNameCharacters(QString(i->identifier.c_str())))
Chris@239:             .arg(m_plugin->getParameter(i->identifier));
Chris@66:     }
Chris@66: 
Chris@75:     RealTimePluginInstance *rtpi =
Chris@75:         dynamic_cast<RealTimePluginInstance *>(m_plugin);
Chris@75:     if (rtpi) {
Chris@75:         std::map<std::string, std::string> configurePairs =
Chris@75:             rtpi->getConfigurePairs();
Chris@75:         QString config;
Chris@75:         for (std::map<std::string, std::string>::iterator i = configurePairs.begin();
Chris@75:              i != configurePairs.end(); ++i) {
Chris@75:             QString key = i->first.c_str();
Chris@75:             QString value = i->second.c_str();
Chris@81:             key = encodeConfigurationChars(key);
Chris@81:             value = encodeConfigurationChars(value);
Chris@75:             if (config != "") config += ";";
Chris@75:             config += QString("%1=%2").arg(key).arg(value);
Chris@75:         }
Chris@75:         if (config != "") {
Chris@314:             stream << QString("configuration=\"%1\" ")
Chris@75:                 .arg(encodeEntities(config));
Chris@75:         }
Chris@75:     }
Chris@75: 
Chris@314:     stream << "/>\n";
Chris@66: }
Chris@66: 
Chris@66: #define CHECK_ATTRIBUTE(ATTRIBUTE, ACCESSOR) \
Chris@66:     QString ATTRIBUTE = attrs.value(#ATTRIBUTE); \
Chris@66:     if (ATTRIBUTE != "" && ATTRIBUTE != ACCESSOR().c_str()) { \
Chris@843:         cerr << "WARNING: PluginXml::setParameters: Plugin " \
Chris@66:                   << #ATTRIBUTE << " does not match (attributes have \"" \
Chris@686:                   << ATTRIBUTE << "\", my " \
Chris@843:                   << #ATTRIBUTE << " is \"" << ACCESSOR() << "\")" << endl; \
Chris@66:     }
Chris@66: 
Chris@66: void
Chris@66: PluginXml::setParameters(const QXmlAttributes &attrs)
Chris@66: {
Chris@239:     CHECK_ATTRIBUTE(identifier, m_plugin->getIdentifier);
Chris@66:     CHECK_ATTRIBUTE(name, m_plugin->getName);
Chris@66:     CHECK_ATTRIBUTE(description, m_plugin->getDescription);
Chris@66:     CHECK_ATTRIBUTE(maker, m_plugin->getMaker);
Chris@66:     CHECK_ATTRIBUTE(copyright, m_plugin->getCopyright);
Chris@66: 
Chris@66:     bool ok;
Chris@66:     int version = attrs.value("version").trimmed().toInt(&ok);
Chris@66:     if (ok && version != m_plugin->getPluginVersion()) {
Chris@843:         cerr << "WARNING: PluginXml::setParameters: Plugin version does not match (attributes have " << version << ", my version is " << m_plugin->getPluginVersion() << ")" << endl;
Chris@66:     }
Chris@66: 
Chris@75:     RealTimePluginInstance *rtpi =
Chris@75:         dynamic_cast<RealTimePluginInstance *>(m_plugin);
Chris@75:     if (rtpi) {
Chris@75:         QString config = attrs.value("configuration");
Chris@75:         if (config != "") {
Chris@75:             QStringList configList = config.split(";");
Chris@75:             for (QStringList::iterator i = configList.begin();
Chris@75:                  i != configList.end(); ++i) {
Chris@75:                 QStringList kv = i->split("=");
Chris@75:                 if (kv.count() < 2) {
Chris@844:                     cerr << "WARNING: PluginXml::setParameters: Malformed configure pair string: \"" << *i << "\"" << endl;
Chris@75:                     continue;
Chris@75:                 }
Chris@75:                 QString key(kv[0]), value(kv[1]);
Chris@81:                 key = decodeConfigurationChars(key);
Chris@81:                 value = decodeConfigurationChars(value);
Chris@75:                 rtpi->configure(key.toStdString(), value.toStdString());
Chris@75:             }
Chris@75:         }
Chris@75:     }
Chris@75: 
Chris@66:     if (!m_plugin->getPrograms().empty()) {
Chris@66:         m_plugin->selectProgram(attrs.value("program").toStdString());
Chris@66:     }
Chris@66: 
Chris@66:     Vamp::PluginBase::ParameterList parameters =
Chris@66:         m_plugin->getParameterDescriptors();
Chris@66: 
Chris@66:     for (Vamp::PluginBase::ParameterList::const_iterator i =
Chris@66:              parameters.begin(); i != parameters.end(); ++i) {
Chris@66: 
Chris@239:         QString pname = QString("param-%1")
Chris@66:             .arg(stripInvalidParameterNameCharacters
Chris@239:                  (QString(i->identifier.c_str())));
Chris@66: 
Chris@239:         if (attrs.value(pname) == "") {
Chris@690: //            SVDEBUG << "PluginXml::setParameters: no parameter \"" << i->name << "\" (attribute \"" << name << "\")" << endl;
Chris@66:             continue;
Chris@66:         }
Chris@66: 
Chris@66:         bool ok;
Chris@239:         float value = attrs.value(pname).trimmed().toFloat(&ok);
Chris@66:         if (ok) {
Chris@690: //            SVDEBUG << "PluginXml::setParameters: setting parameter \""
Chris@687: //                      << i->identifier << "\" to value " << value << endl;
Chris@239:             m_plugin->setParameter(i->identifier, value);
Chris@66:         } else {
Chris@843:             cerr << "WARNING: PluginXml::setParameters: Invalid value \"" << attrs.value(pname) << "\" for parameter \"" << i->identifier << "\" (attribute \"" << pname << "\")" << endl;
Chris@66:         }
Chris@66:     }
Chris@66: }
Chris@66: 
Chris@66: void
Chris@66: PluginXml::setParametersFromXml(QString xml)
Chris@66: {
Chris@66:     QDomDocument doc;
Chris@66: 
Chris@66:     QString error;
Chris@66:     int errorLine;
Chris@66:     int errorColumn;
Chris@66: 
Chris@690: //    SVDEBUG << "PluginXml::setParametersFromXml: XML is \""
Chris@845: //              << xml << "\"" << endl;
Chris@185: 
Chris@66:     if (!doc.setContent(xml, false, &error, &errorLine, &errorColumn)) {
Chris@843:         cerr << "PluginXml::setParametersFromXml: Error in parsing XML: " << error << " at line " << errorLine << ", column " << errorColumn << endl;
Chris@843:         cerr << "Input follows:" << endl;
Chris@843:         cerr << xml << endl;
Chris@843:         cerr << "Input ends." << endl;
Chris@66:         return;
Chris@66:     }
Chris@66: 
Chris@66:     QDomElement pluginElt = doc.firstChildElement("plugin");
Chris@66:     QDomNamedNodeMap attrNodes = pluginElt.attributes();
Chris@66:     QXmlAttributes attrs;
Chris@66: 
Chris@929:     for (int i = 0; i < attrNodes.length(); ++i) {
Chris@66:         QDomAttr attr = attrNodes.item(i).toAttr();
Chris@66:         if (attr.isNull()) continue;
Chris@690: //        SVDEBUG << "PluginXml::setParametersFromXml: Adding attribute \"" << attr.name()//                  << "\" with value \"" << attr.value() << "\"" << endl;
Chris@66:         attrs.append(attr.name(), "", "", attr.value());
Chris@66:     }
Chris@66: 
Chris@66:     setParameters(attrs);
Chris@66: }
Chris@163: 
Chris@66: QString
Chris@66: PluginXml::stripInvalidParameterNameCharacters(QString s) const
Chris@66: {
Chris@66:     s.replace(QRegExp("[^a-zA-Z0-9_]*"), "");
Chris@66:     return s;
Chris@66: }
Chris@66: