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
|