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 : #include "mscloader.h"
23 :
24 : #include <memory>
25 :
26 : #include "global/io/buffer.h"
27 : #include "global/types/retval.h"
28 :
29 : #include "../engravingerrors.h"
30 :
31 : #include "../types/types.h"
32 :
33 : #include "../dom/masterscore.h"
34 : #include "../dom/excerpt.h"
35 : #include "../dom/imageStore.h"
36 :
37 : #include "engraving/automation/automationdata.h"
38 : #include "engraving/automation/internal/automationrw.h"
39 :
40 : #include "compat/compatutils.h"
41 : #include "compat/readstyle.h"
42 :
43 : #include "rwregister.h"
44 : #include "xmlreader.h"
45 : #include "inoutdata.h"
46 :
47 : #include "log.h"
48 :
49 : using namespace mu;
50 : using namespace muse;
51 : using namespace muse::io;
52 : using namespace mu::engraving;
53 : using namespace mu::engraving::rw;
54 :
55 1019 : static RetVal<IReaderPtr> makeReader(int version, bool ignoreVersionError)
56 : {
57 1019 : if (!ignoreVersionError) {
58 1012 : if (version > Constants::MSC_VERSION) {
59 0 : return RetVal<IReaderPtr>(make_ret(Err::FileTooNew));
60 : }
61 :
62 1012 : if (version < 114) {
63 0 : return RetVal<IReaderPtr>(make_ret(Err::FileTooOld));
64 : }
65 :
66 1012 : if (version == 300) {
67 0 : return RetVal<IReaderPtr>(make_ret(Err::FileOld300Format));
68 : }
69 : }
70 :
71 1019 : return RetVal<IReaderPtr>::make_ok(RWRegister::reader(version));
72 : }
73 :
74 1008 : Ret MscLoader::loadMscz(MasterScore* masterScore, const MscReader& mscReader, rw::ReadInOutData* inOut,
75 : bool ignoreVersionError)
76 : {
77 1016 : TRACEFUNC;
78 :
79 : using namespace mu::engraving;
80 :
81 1008 : IF_ASSERT_FAILED(mscReader.isOpened()) {
82 0 : return make_ret(Err::FileOpenError, mscReader.params().filePath);
83 : }
84 :
85 1008 : ScoreLoad sl;
86 :
87 1008 : if (mscReader.isContainer()) {
88 : // Read style
89 : {
90 62 : ByteArray styleData = mscReader.readStyleFile();
91 62 : if (!styleData.empty()) {
92 61 : auto buf = Buffer::opened(IODevice::ReadOnly, &styleData);
93 61 : masterScore->style().read(&buf);
94 61 : if (inOut) {
95 0 : inOut->originalSpatium = masterScore->style().spatium();
96 : }
97 61 : }
98 62 : }
99 :
100 : // Read ChordList
101 : {
102 62 : bool chordListOk = false;
103 62 : ByteArray chordListData = mscReader.readChordListFile();
104 62 : if (!chordListData.empty()) {
105 0 : auto buf = Buffer::opened(IODevice::ReadOnly, &chordListData);
106 :
107 0 : chordListOk = masterScore->chordList()->read(&buf);
108 0 : }
109 :
110 62 : masterScore->chordList()->setCustomChordList(chordListOk);
111 :
112 62 : if (!chordListOk) {
113 : // See also ReadChordListHook::validate()
114 62 : MStyle& style = masterScore->style();
115 62 : ChordList* chordList = masterScore->chordList();
116 :
117 62 : bool custom = style.styleV(Sid::chordStyle).value<ChordStylePreset>() == ChordStylePreset::CUSTOM;
118 62 : chordList->setCustomChordList(custom);
119 :
120 : // Ensure that `checkChordList` loads the default chord list
121 62 : chordList->unload();
122 : }
123 62 : }
124 :
125 : // Read images
126 : {
127 62 : if (!MScore::noImages) {
128 62 : std::vector<String> images = mscReader.imageFileNames();
129 62 : for (const String& name : images) {
130 0 : imageStore.add(name.toStdString(), mscReader.readImageFile(name));
131 : }
132 62 : }
133 : }
134 : }
135 :
136 1008 : ReadInOutData masterReadOutData;
137 1008 : if (!inOut) {
138 1008 : inOut = &masterReadOutData;
139 : }
140 :
141 1008 : Ret ret = muse::make_ok();
142 :
143 : // Read score
144 : {
145 1008 : ByteArray scoreData = mscReader.readScoreFile();
146 1008 : String docName = masterScore->fileInfo()->fileName().toString();
147 :
148 1008 : compat::ReadStyleHook styleHook(masterScore, scoreData, docName);
149 :
150 1008 : XmlReader xml(scoreData);
151 1008 : xml.setDocName(docName);
152 :
153 1008 : ret = readMasterScore(masterScore, xml, ignoreVersionError, inOut, &styleHook);
154 1008 : }
155 :
156 : // Read excerpts
157 1008 : if (ret && masterScore->mscVersion() >= 400 && mscReader.isContainer()) {
158 62 : std::vector<String> excerptFileNames = mscReader.excerptFileNames();
159 73 : for (const String& excerptFileName : excerptFileNames) {
160 11 : Score* partScore = masterScore->createScore();
161 :
162 11 : compat::ReadStyleHook::setupDefaultStyle(partScore);
163 :
164 11 : Excerpt* ex = new Excerpt(masterScore);
165 11 : ex->setExcerptScore(partScore);
166 11 : ex->setFileName(excerptFileName);
167 :
168 11 : ByteArray excerptStyleData = mscReader.readExcerptStyleFile(excerptFileName);
169 11 : auto excerptStyleBuf = Buffer::opened(IODevice::ReadOnly, &excerptStyleData);
170 11 : partScore->style().read(&excerptStyleBuf);
171 :
172 11 : ByteArray excerptData = mscReader.readExcerptFile(excerptFileName);
173 :
174 11 : XmlReader xml(excerptData);
175 11 : xml.setDocName(excerptFileName);
176 :
177 11 : ReadInOutData partReadInData;
178 11 : partReadInData.links = inOut->links;
179 :
180 11 : RetVal<IReaderPtr> reader = makeReader(masterScore->mscVersion(), ignoreVersionError);
181 11 : if (!reader.ret) {
182 0 : ret = reader.ret;
183 0 : break;
184 : }
185 :
186 11 : ret = reader.val->readScoreFile(partScore, xml, &partReadInData);
187 11 : if (!ret) {
188 0 : break;
189 : }
190 :
191 11 : Excerpt::linkMeasures(partScore, masterScore);
192 :
193 11 : if (ex->name().empty()) {
194 : // If no excerpt name tag was found while reading, try the "partName" meta tag
195 0 : const String nameFromMeta = partScore->metaTag(u"partName");
196 :
197 0 : if (nameFromMeta.empty()) {
198 : // If that's also empty, fall back to the filename
199 0 : ex->setName(excerptFileName, /*saveAndNotify=*/ false);
200 : } else {
201 0 : ex->setName(nameFromMeta, /*saveAndNotify=*/ false);
202 : }
203 0 : }
204 :
205 11 : masterScore->addExcerpt(ex);
206 11 : }
207 62 : }
208 :
209 : // Compatibility conversions
210 : // NOTE: must be done after all score and parts have been read
211 1008 : compat::CompatUtils::doCompatibilityConversions(masterScore);
212 :
213 : // Read automation
214 : {
215 1008 : ByteArray ba = mscReader.readAutomationJsonFile();
216 1008 : AutomationDataPtr automationData = std::make_shared<AutomationData>();
217 1008 : AutomationRW::read(*automationData, ba);
218 1008 : masterScore->setAutomationData(automationData);
219 1008 : }
220 :
221 1008 : return ret;
222 1008 : }
223 :
224 1008 : Ret MscLoader::readMasterScore(MasterScore* score, XmlReader& e, bool ignoreVersionError, ReadInOutData* out,
225 : compat::ReadStyleHook* styleHook)
226 : {
227 1008 : while (e.readNextStartElement()) {
228 1008 : if (e.name() == "museScore") {
229 1008 : const String version = e.attribute("version");
230 1008 : const StringList sl = version.split(u'.');
231 1008 : score->setMscVersion(sl[0].toInt() * 100 + sl[1].toInt());
232 :
233 1008 : RetVal<IReaderPtr> reader = makeReader(score->mscVersion(), ignoreVersionError);
234 1008 : if (!reader.ret) {
235 0 : return reader.ret;
236 : }
237 :
238 1008 : IF_ASSERT_FAILED(reader.val) {
239 0 : return make_ret(Err::FileUnknownError);
240 : }
241 :
242 : //! NOTE We need to achieve that the default style corresponds to the version in which the score is created.
243 : //! The values that the user changed will be written on over (only they are stored in the `mscz` file)
244 : //! For version 4.0 (400), this does not need to be done,
245 : //! because starting from version 4.0 the entire style is stored in a file,
246 : //! respectively, the entire style will be loaded, which was when the score was created.
247 1008 : if (styleHook && score->mscVersion() < 400) {
248 473 : styleHook->setupDefaultStyle();
249 : }
250 :
251 1008 : if (score->mscVersion() >= 400) {
252 : //! NOTE: make sure we have a chord list
253 : //! Load the default chord list otherwise
254 535 : score->checkChordList();
255 : }
256 :
257 1008 : Ret ret = reader.val->readScoreFile(score, e, out);
258 :
259 1008 : score->setExcerptsChanged(false);
260 :
261 1008 : return ret;
262 1008 : } else {
263 0 : e.unknown();
264 : }
265 : }
266 :
267 0 : return make_ret(Err::FileCriticallyCorrupted, e.errorString());
268 : }
|