LCOV - code coverage report
Current view: top level - src/importexport/musicxml/internal/import - musicxmlvalidation.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 100.0 % 2 2
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "musicxmlvalidation.h"
      24              : 
      25              : #ifdef MUSICXML_NO_VALIDATION
      26              : using namespace mu;
      27              : using namespace mu::iex::musicxml;
      28              : using namespace mu::engraving;
      29              : 
      30          309 : Err MusicXmlValidation::validate(const muse::String&, const muse::ByteArray&)
      31              : {
      32          309 :     return Err::NoError;
      33              : }
      34              : 
      35              : #else
      36              : 
      37              : #include <QAbstractMessageHandler>
      38              : #include <QXmlSchema>
      39              : #include <QXmlSchemaValidator>
      40              : #include <QMessageBox>
      41              : #include <QDomDocument>
      42              : 
      43              : #include "engraving/dom/mscore.h"
      44              : 
      45              : #include "musicxmlsupport.h"
      46              : 
      47              : #include "log.h"
      48              : 
      49              : using namespace mu;
      50              : using namespace mu::iex::musicxml;
      51              : using namespace mu::engraving;
      52              : 
      53              : //---------------------------------------------------------
      54              : //   ValidatorMessageHandler
      55              : //---------------------------------------------------------
      56              : 
      57              : /**
      58              :  Message handler for the MusicXML schema validator QXmlSchemaValidator.
      59              :  */
      60              : 
      61              : class ValidatorMessageHandler : public QAbstractMessageHandler
      62              : {
      63              : public:
      64              :     ValidatorMessageHandler()
      65              :         : QAbstractMessageHandler(0) {}
      66              :     QString getErrors() const { return m_errors; }
      67              : protected:
      68              :     virtual void handleMessage(QtMsgType type, const QString& description, const QUrl& identifier, const QSourceLocation& sourceLocation);
      69              : private:
      70              :     QString m_errors;
      71              : };
      72              : 
      73              : void ValidatorMessageHandler::handleMessage(QtMsgType type, const QString& description,
      74              :                                             const QUrl& /* identifier */, const QSourceLocation& sourceLocation)
      75              : {
      76              :     // convert description from html to text
      77              :     QDomDocument desc;
      78              :     QString contentError;
      79              :     int contentLine;
      80              :     int contentColumn;
      81              :     if (!desc.setContent(description, false, &contentError, &contentLine,
      82              :                          &contentColumn)) {
      83              :         LOGD("ValidatorMessageHandler: could not parse validation error line %d column %d: %s",
      84              :              contentLine, contentColumn, qPrintable(contentError));
      85              :         return;
      86              :     }
      87              : 
      88              :     QDomElement e = desc.documentElement();
      89              :     if (e.tagName() != "html") {
      90              :         LOGD("ValidatorMessageHandler: description is not html");
      91              :         return;
      92              :     }
      93              : 
      94              :     QString typeStr;
      95              :     switch (type) {
      96              :     case 0:  typeStr = muse::qtrc("iex_musicxml", "Debug message:");
      97              :         break;
      98              :     case 1:  typeStr = muse::qtrc("iex_musicxml", "Warning:");
      99              :         break;
     100              :     case 2:  typeStr = muse::qtrc("iex_musicxml", "Critical error:");
     101              :         break;
     102              :     case 3:  typeStr = muse::qtrc("iex_musicxml", "Fatal error:");
     103              :         break;
     104              :     default: typeStr = muse::qtrc("iex_musicxml", "Unknown error:");
     105              :         break;
     106              :     }
     107              : 
     108              :     QString errorStr = typeStr + " " + errorStringWithLocation(sourceLocation.byteOffset(), e.text());
     109              : 
     110              :     // append error, separated by newline if necessary
     111              :     if (!m_errors.isEmpty()) {
     112              :         m_errors += "\n";
     113              :     }
     114              :     m_errors += errorStr;
     115              : }
     116              : 
     117              : //---------------------------------------------------------
     118              : //   initMusicXmlSchema
     119              : //    return false on error
     120              : //---------------------------------------------------------
     121              : 
     122              : static bool initMusicXmlSchema(QXmlSchema& schema)
     123              : {
     124              :     // read the MusicXML schema from the application resources
     125              :     QFile schemaFile(":/schema/musicxml.xsd");
     126              :     if (!schemaFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
     127              :         LOGE("initMusicXmlSchema() could not open resource musicxml.xsd");
     128              :         return false;
     129              :     }
     130              : 
     131              :     // copy the schema into a QByteArray and fixup xs:imports,
     132              :     // using a path to the application resources instead of to www.musicxml.org
     133              :     // to prevent downloading from the net
     134              :     QByteArray schemaBa;
     135              :     QTextStream schemaStream(&schemaFile);
     136              :     while (!schemaStream.atEnd()) {
     137              :         QString line = schemaStream.readLine();
     138              :         if (line.contains("xs:import")) {
     139              :             line.replace("http://www.musicxml.org/xsd", "qrc:///schema");
     140              :         }
     141              :         schemaBa += line.toUtf8();
     142              :         schemaBa += "\n";
     143              :     }
     144              : 
     145              :     // load and validate the schema
     146              :     schema.load(schemaBa);
     147              :     if (!schema.isValid()) {
     148              :         LOGE("initMusicXmlSchema() internal error: MusicXML schema is invalid");
     149              :         return false;
     150              :     }
     151              : 
     152              :     return true;
     153              : }
     154              : 
     155              : //---------------------------------------------------------
     156              : //   musicXmlValidationErrorDialog
     157              : //---------------------------------------------------------
     158              : 
     159              : /**
     160              :  Show a dialog displaying the MusicXML validation error(s)
     161              :  and asks the user if he wants to try to load the file anyway.
     162              :  Return QMessageBox::Yes (try anyway) or QMessageBox::No (don't)
     163              :  */
     164              : 
     165              : static int musicXmlValidationErrorDialog(QString text, QString detailedText)
     166              : {
     167              :     QMessageBox errorDialog;
     168              :     errorDialog.setIcon(QMessageBox::Question);
     169              :     errorDialog.setText(text);
     170              :     errorDialog.setInformativeText(muse::qtrc("iex_musicxml", "Do you want to try to load this file anyway?"));
     171              :     errorDialog.setDetailedText(detailedText);
     172              :     errorDialog.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
     173              :     errorDialog.setDefaultButton(QMessageBox::No);
     174              :     return errorDialog.exec();
     175              : }
     176              : 
     177              : Err MusicXmlValidation::validate(const String& name, const muse::ByteArray& data)
     178              : {
     179              :     //QElapsedTimer t;
     180              :     //t.start();
     181              : 
     182              :     // initialize the schema
     183              :     ValidatorMessageHandler messageHandler;
     184              :     QXmlSchema schema;
     185              :     schema.setMessageHandler(&messageHandler);
     186              :     if (!initMusicXmlSchema(schema)) {
     187              :         return Err::FileBadFormat;      // appropriate error message has been printed by initMusicXmlSchema
     188              :     }
     189              :     // validate the data
     190              :     QXmlSchemaValidator validator(schema);
     191              :     const QByteArray qdata = data.toQByteArrayNoCopy();
     192              :     bool valid = validator.validate(qdata, QUrl::fromLocalFile(name));
     193              :     //LOGD("Validation time elapsed: %d ms", t.elapsed());
     194              : 
     195              :     if (!valid) {
     196              :         LOGD("importMusicXml() file '%s' is not a valid MusicXML file", muPrintable(name));
     197              :         QString strErr = muse::qtrc("iex_musicxml", "File “%1” is not a valid MusicXML file.").arg(name);
     198              :         if (MScore::noGui) {
     199              :             return Err::NoError;         // might as well try anyhow in converter mode
     200              :         }
     201              :         if (musicXmlValidationErrorDialog(strErr, messageHandler.getErrors()) != QMessageBox::Yes) {
     202              :             return Err::UserAbort;
     203              :         }
     204              :     }
     205              : 
     206              :     return Err::NoError;
     207              : }
     208              : 
     209              : #endif // MUSICXML_NO_VALIDATION
        

Generated by: LCOV version 2.0-1