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 "mscmetareader.h"
24 :
25 : #include <QPixmap>
26 :
27 : #include "global/io/buffer.h"
28 : #include "global/serialization/xmlstreamreader.h"
29 : #include "log.h"
30 :
31 : #include "engraving/infrastructure/mscreader.h"
32 :
33 : #include "types/projectmeta.h"
34 : #include "types/projecttypes.h"
35 :
36 : using namespace muse;
37 : using namespace muse::io;
38 : using namespace mu::project;
39 : using namespace mu::engraving;
40 :
41 : // emulates QXmlStreamReader::readElementText(QXmlStreamReader::IncludeChildElements)
42 4 : static std::string readTextAndChildrenText(XmlStreamReader& reader)
43 : {
44 4 : if (!reader.isStartElement()) {
45 0 : return std::string();
46 : }
47 :
48 4 : std::string text;
49 : while (true) {
50 8 : switch (reader.readNext()) {
51 0 : case XmlStreamReader::Comment:
52 0 : break;
53 4 : case XmlStreamReader::Characters:
54 4 : text += reader.asciiText();
55 4 : break;
56 0 : case XmlStreamReader::StartElement:
57 0 : text += readTextAndChildrenText(reader);
58 0 : break;
59 4 : case XmlStreamReader::EndElement:
60 4 : return text;
61 0 : default:
62 0 : LOGE() << "Unexpected token: " << reader.tokenString();
63 0 : return text;
64 : }
65 : }
66 4 : }
67 :
68 0 : muse::RetVal<QPixmap> MscMetaReader::readThumbnail(const muse::io::path_t& filePath) const
69 : {
70 0 : MscReader msczReader;
71 0 : Ret ret = prepareReader(filePath, msczReader);
72 0 : if (!ret) {
73 0 : return ret;
74 : }
75 :
76 0 : RetVal<QPixmap> thumbnail;
77 0 : thumbnail.ret = make_ok();
78 0 : const ByteArray thumbnailData = msczReader.readThumbnailFile();
79 0 : if (thumbnailData.empty()) {
80 0 : LOGD() << "Can't find thumbnail";
81 : } else {
82 0 : if (!thumbnail.val.loadFromData(thumbnailData.toQByteArrayNoCopy(), "PNG")) {
83 0 : thumbnail.ret = make_ret(Ret::Code::BadData);
84 : }
85 : }
86 :
87 0 : return thumbnail;
88 0 : }
89 :
90 2 : RetVal<ProjectMeta> MscMetaReader::readMeta(const muse::io::path_t& filePath) const
91 : {
92 2 : MscReader msczReader;
93 2 : Ret ret = prepareReader(filePath, msczReader);
94 2 : if (!ret) {
95 0 : return ret;
96 : }
97 :
98 : // Read score meta
99 2 : auto scoreData = Buffer::opened(IODevice::ReadOnly, msczReader.readScoreFile());
100 2 : XmlStreamReader xmlReader(&scoreData);
101 :
102 2 : RetVal<ProjectMeta> meta;
103 2 : meta.ret = make_ok();
104 2 : doReadMeta(xmlReader, meta.val);
105 :
106 : // Read thumbnail
107 2 : ByteArray thumbnailData = msczReader.readThumbnailFile();
108 2 : if (thumbnailData.empty()) {
109 0 : LOGD() << "Can't find thumbnail";
110 : } else {
111 2 : if (!meta.val.thumbnail.loadFromData(thumbnailData.toQByteArrayNoCopy(), "PNG")) {
112 0 : meta.ret = make_ret(Ret::Code::BadData);
113 : }
114 : }
115 :
116 2 : meta.val.filePath = filePath;
117 :
118 2 : return meta;
119 2 : }
120 :
121 1 : muse::RetVal<CloudProjectInfo> MscMetaReader::readCloudProjectInfo(const muse::io::path_t& filePath) const
122 : {
123 2 : TRACEFUNC;
124 :
125 1 : MscReader msczReader;
126 1 : Ret ret = prepareReader(filePath, msczReader);
127 1 : if (!ret) {
128 0 : return ret;
129 : }
130 :
131 : // Read score meta
132 1 : auto scoreData = Buffer::opened(IODevice::ReadOnly, msczReader.readScoreFile());
133 1 : XmlStreamReader xmlReader(&scoreData);
134 :
135 1 : ProjectMeta meta;
136 1 : doReadMeta(xmlReader, meta);
137 :
138 1 : RetVal<CloudProjectInfo> info;
139 1 : info.ret = make_ok();
140 1 : info.val.sourceUrl = meta.source;
141 1 : info.val.revisionId = meta.additionalTags[SOURCE_REVISION_ID_TAG].toInt();
142 :
143 1 : return info;
144 1 : }
145 :
146 3 : Ret MscMetaReader::prepareReader(const muse::io::path_t& filePath, MscReader& reader) const
147 : {
148 3 : Ret ret = fileSystem()->exists(filePath);
149 3 : if (!ret) {
150 0 : LOGE() << "File not exists: " << filePath;
151 0 : return ret;
152 : }
153 :
154 3 : MscReader::Params params;
155 3 : params.filePath = filePath.toQString();
156 3 : params.mode = mscIoModeBySuffix(io::suffix(filePath));
157 3 : if (params.mode == MscIoMode::Unknown) {
158 0 : return make_ret(Ret::Code::InternalError);
159 : }
160 :
161 3 : reader.setParams(params);
162 3 : if (!reader.open()) {
163 0 : return make_ret(Ret::Code::InternalError);
164 : }
165 :
166 3 : return make_ok();
167 3 : }
168 :
169 1 : MscMetaReader::RawMeta MscMetaReader::doReadBox(XmlStreamReader& xmlReader) const
170 : {
171 1 : RawMeta meta;
172 :
173 6 : while (xmlReader.readNextStartElement()) {
174 5 : if (xmlReader.name() == "Text") {
175 4 : bool isTitle = false;
176 4 : bool isSubtitle = false;
177 4 : bool isComposer = false;
178 4 : bool isLyricist = false;
179 12 : while (xmlReader.readNextStartElement()) {
180 8 : const std::string tag(xmlReader.name());
181 :
182 8 : if (tag == "style") {
183 8 : const std::string val = xmlReader.readText()
184 4 : .toLower().toStdString();
185 :
186 4 : if (val == "title" || val == "2") {
187 1 : isTitle = true;
188 3 : } else if (val == "composer" || val == "4") {
189 1 : isComposer = true;
190 2 : } else if (val == "subtitle") {
191 1 : isSubtitle = true;
192 1 : } else if (val == "lyricist") {
193 1 : isLyricist = true;
194 : } else {
195 0 : xmlReader.skipCurrentElement();
196 : }
197 8 : } else if (tag == "text") {
198 4 : if (isTitle) {
199 1 : meta.titleStyle = readText(xmlReader);
200 3 : } else if (isSubtitle) {
201 1 : meta.subtitleStyle = readText(xmlReader);
202 2 : } else if (isComposer) {
203 1 : meta.composerStyle = readText(xmlReader);
204 1 : } else if (isLyricist) {
205 1 : meta.lyricistStyle = readText(xmlReader);
206 : } else {
207 0 : xmlReader.skipCurrentElement();
208 : }
209 0 : } else if (tag == "html-data") {
210 0 : if (isTitle) {
211 0 : meta.titleStyleHtml = readText(xmlReader);
212 0 : } else if (isSubtitle) {
213 0 : meta.subtitleStyleHtml = readText(xmlReader);
214 0 : } else if (isComposer) {
215 0 : meta.composerStyleHtml = readText(xmlReader);
216 0 : } else if (isLyricist) {
217 0 : meta.lyricistStyleHtml = readText(xmlReader);
218 : } else {
219 0 : xmlReader.skipCurrentElement();
220 : }
221 : } else {
222 0 : xmlReader.skipCurrentElement();
223 : }
224 8 : }
225 : } else {
226 1 : xmlReader.skipCurrentElement();
227 : }
228 : }
229 :
230 1 : return meta;
231 0 : }
232 :
233 3 : MscMetaReader::RawMeta MscMetaReader::doReadRawMeta(XmlStreamReader& xmlReader) const
234 : {
235 3 : RawMeta meta;
236 :
237 57 : while (xmlReader.readNextStartElement()) {
238 54 : const std::string tag(xmlReader.name());
239 :
240 54 : if (tag == "work-title") {
241 0 : meta.titleTag = xmlReader.readText().toQString();
242 54 : } else if (tag == "metaTag") {
243 24 : const std::string name(xmlReader.asciiAttribute("name"));
244 :
245 24 : if (name == "workTitle") {
246 3 : meta.titleAttribute = readMetaTagText(xmlReader);
247 21 : } else if (name == "composer") {
248 3 : meta.composerAttribute = readMetaTagText(xmlReader);
249 18 : } else if (name == "arranger") {
250 3 : meta.arranger = readMetaTagText(xmlReader);
251 15 : } else if (name == "lyricist") {
252 3 : meta.lyricistAttribute = readMetaTagText(xmlReader);
253 12 : } else if (name == "copyright") {
254 3 : meta.copyright = readMetaTagText(xmlReader);
255 9 : } else if (name == "translator") {
256 3 : meta.translator = readMetaTagText(xmlReader);
257 6 : } else if (name == "creationDate") {
258 3 : meta.creationDate = readMetaTagText(xmlReader);
259 : } else {
260 3 : meta.additionalTags[QString::fromUtf8(name)] = readMetaTagText(xmlReader);
261 : }
262 54 : } else if (tag == "Staff") {
263 3 : if (meta.titleStyle.isEmpty()) {
264 7 : while (xmlReader.readNextStartElement()) {
265 4 : const std::string boxTag(xmlReader.name());
266 :
267 4 : if (boxTag == "HBox"
268 4 : || boxTag == "VBox"
269 3 : || boxTag == "TBox"
270 8 : || boxTag == "FBox") {
271 1 : RawMeta boxMeta = doReadBox(xmlReader);
272 :
273 1 : meta.titleStyle = boxMeta.titleStyle;
274 1 : meta.titleStyleHtml = boxMeta.titleStyleHtml;
275 1 : meta.subtitleStyle = boxMeta.subtitleStyle;
276 1 : meta.subtitleStyleHtml = boxMeta.subtitleStyleHtml;
277 1 : meta.composerStyle = boxMeta.composerStyle;
278 1 : meta.composerStyleHtml = boxMeta.composerStyleHtml;
279 1 : meta.lyricistStyle = boxMeta.lyricistStyle;
280 1 : meta.lyricistStyleHtml = boxMeta.lyricistStyleHtml;
281 1 : } else {
282 3 : xmlReader.skipCurrentElement();
283 : }
284 4 : }
285 : } else {
286 0 : xmlReader.skipCurrentElement();
287 : }
288 27 : } else if (tag == "Part") {
289 3 : meta.partsCount++;
290 3 : xmlReader.skipCurrentElement();
291 : } else {
292 24 : xmlReader.skipCurrentElement();
293 : }
294 54 : }
295 :
296 3 : return meta;
297 0 : }
298 :
299 3 : void MscMetaReader::doReadMeta(XmlStreamReader& xmlReader, ProjectMeta& meta) const
300 : {
301 3 : RawMeta rawMeta;
302 :
303 6 : while (xmlReader.readNextStartElement()) {
304 3 : if (xmlReader.name() == "museScore") {
305 3 : const std::string version(xmlReader.asciiAttribute("version"));
306 3 : bool suitedVersion = version.rfind("1", 0) == 0;
307 :
308 3 : if (suitedVersion) {
309 0 : rawMeta = doReadRawMeta(xmlReader);
310 : } else {
311 6 : while (xmlReader.readNextStartElement()) {
312 3 : if (xmlReader.name() == "Score") {
313 3 : rawMeta = doReadRawMeta(xmlReader);
314 : } else {
315 0 : xmlReader.skipCurrentElement();
316 : }
317 : }
318 : }
319 3 : } else {
320 0 : xmlReader.skipCurrentElement();
321 : }
322 : }
323 :
324 3 : if (!rawMeta.titleStyle.isEmpty()) {
325 1 : meta.title = simplified(rawMeta.titleStyle);
326 2 : } else if (!rawMeta.titleStyleHtml.isEmpty()) {
327 0 : meta.title = simplified(rawMeta.titleStyleHtml);
328 2 : } else if (!rawMeta.titleAttribute.isEmpty()) {
329 2 : meta.title = simplified(rawMeta.titleAttribute);
330 : } else {
331 0 : meta.title = simplified(rawMeta.titleTag);
332 : }
333 :
334 3 : if (!rawMeta.subtitleStyle.isEmpty()) {
335 1 : meta.subtitle = simplified(rawMeta.subtitleStyle);
336 2 : } else if (!rawMeta.subtitleStyleHtml.isEmpty()) {
337 0 : meta.subtitle = simplified(rawMeta.subtitleStyleHtml);
338 : }
339 :
340 3 : if (!rawMeta.composerStyle.isEmpty()) {
341 1 : meta.composer = simplified(rawMeta.composerStyle);
342 2 : } else if (!rawMeta.composerStyleHtml.isEmpty()) {
343 0 : meta.composer = simplified(rawMeta.composerStyleHtml);
344 : } else {
345 2 : meta.composer = simplified(rawMeta.composerAttribute);
346 : }
347 :
348 3 : if (!rawMeta.lyricistStyle.isEmpty()) {
349 1 : meta.lyricist = simplified(rawMeta.lyricistStyle);
350 2 : } else if (!rawMeta.lyricistStyleHtml.isEmpty()) {
351 0 : meta.lyricist = simplified(rawMeta.lyricistStyleHtml);
352 : } else {
353 2 : meta.lyricist = simplified(rawMeta.lyricistAttribute);
354 : }
355 :
356 3 : meta.copyright = simplified(rawMeta.copyright);
357 3 : meta.translator = simplified(rawMeta.translator);
358 3 : meta.arranger = simplified(rawMeta.arranger);
359 3 : meta.partsCount = rawMeta.partsCount;
360 3 : meta.creationDate = QDate::fromString(rawMeta.creationDate, "yyyy-MM-dd");
361 3 : meta.additionalTags = std::move(rawMeta.additionalTags);
362 3 : }
363 :
364 4 : QString MscMetaReader::formatFromXml(const std::string& xml) const
365 : {
366 4 : size_t bbegin = xml.find("<body");
367 :
368 4 : if (bbegin != std::string::npos) {
369 0 : size_t bend = xml.find("</body>", bbegin);
370 0 : std::string body = xml.substr(bbegin, (bend - bbegin));
371 0 : return QString::fromStdString(MscMetaReader::cutXmlTags(body));
372 0 : }
373 4 : return QString::fromStdString(MscMetaReader::cutXmlTags(xml));
374 : }
375 :
376 0 : QString MscMetaReader::format(const std::string& str) const
377 : {
378 0 : std::string fin = MscMetaReader::cutXmlTags(str);
379 0 : return simplified(fin);
380 0 : }
381 :
382 19 : QString MscMetaReader::simplified(const QString& str) const
383 : {
384 19 : return simplified(str.toStdString());
385 : }
386 :
387 19 : QString MscMetaReader::simplified(const std::string& str) const
388 : {
389 19 : std::string fin;
390 342 : for (size_t index = 0; str[index]; ++index) {
391 323 : if (str[index] == '\"' || str[index] == '\n') {
392 0 : fin += '\\';
393 : }
394 323 : fin += str[index];
395 : }
396 :
397 38 : return QString::fromStdString(fin);
398 19 : }
399 :
400 4 : std::string MscMetaReader::cutXmlTags(const std::string& str) const
401 : {
402 4 : std::string fin;
403 :
404 69 : for (size_t index = 0; str[index]; ++index) {
405 65 : if (str[index] == '<') {
406 0 : for (; str[index + 1] && str[index] != '>'; ++index) {
407 : }
408 0 : continue;
409 : }
410 65 : fin += str[index];
411 : }
412 :
413 4 : return fin;
414 0 : }
415 :
416 4 : QString MscMetaReader::readText(XmlStreamReader& xmlReader) const
417 : {
418 4 : const std::string str = readTextAndChildrenText(xmlReader);
419 :
420 8 : return formatFromXml(str);
421 4 : }
422 :
423 24 : QString MscMetaReader::readMetaTagText(XmlStreamReader& xmlReader) const
424 : {
425 24 : const std::string_view metaText = xmlReader.readAsciiText();
426 :
427 24 : return QString::fromUtf8(metaText);
428 : }
|