LCOV - code coverage report
Current view: top level - src/project/internal - mscmetareader.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 73.8 % 260 192
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 "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              : }
        

Generated by: LCOV version 2.0-1