diff --git a/mscxyz/score_file_classes.py b/mscxyz/score_file_classes.py index e0004bf..6bc5183 100644 --- a/mscxyz/score_file_classes.py +++ b/mscxyz/score_file_classes.py @@ -234,6 +234,8 @@ class MscoreXmlTree(MscoreFile): :param relpath: The relative (or absolute) path of a MuseScore file. """ + xml_tree: _ElementTree + version_major: int """The major MuseScore version, for example 2 or 3""" @@ -243,19 +245,38 @@ class MscoreXmlTree(MscoreFile): def __init__(self, relpath: str) -> None: super(MscoreXmlTree, self).__init__(relpath) try: - self.xml_tree: _ElementTree = lxml.etree.parse(self.loadpath) + self.xml_tree = lxml.etree.parse(self.loadpath) except lxml.etree.XMLSyntaxError as e: self.errors.append(e) else: self.xml_root: _Element = self.xml_tree.getroot() - musescore: _XPathObject = self.xml_tree.xpath("/museScore") - version = musescore[0].get("version") - self.version_major = int(version.split(".")[0]) - self.version = float(version) - - def add_sub_element(self, root_tag, tag, text: str) -> None: - tag: _Element = lxml.etree.SubElement(root_tag, tag) - tag.text = text + self.version = self.get_version() + self.version_major = int(self.version) + + def get_version(self) -> float: + """ + Get the version number of the MuseScore file. + + :return: The version number as a float. + :raises ValueError: If the version number cannot be retrieved. + """ + version: _XPathObject = self.xml_tree.xpath("number(/museScore[1]/@version)") + if isinstance(version, float): + return version + raise ValueError("Could not get version number") + + def add_sub_element(self, root_tag: _Element, tag: str, text: str) -> _Element: + """ + Adds a sub-element to the given root element with the specified tag and text. + + :param root_tag: The root element to which the sub-element will be added. + :param tag: The tag name of the sub-element. + :param text: The text content of the sub-element. + :return: The newly created sub-element. + """ + element: _Element = lxml.etree.SubElement(root_tag, tag) + element.text = text + return element def strip_tags(self, *tag_names: str) -> None: """Delete / strip some tag names.""" diff --git a/tests/files_mscore4/simple.mscz b/tests/files_mscore4/simple.mscz new file mode 100644 index 0000000..2a4fe79 Binary files /dev/null and b/tests/files_mscore4/simple.mscz differ diff --git a/tests/files_mscore4/simple/META-INF/container.xml b/tests/files_mscore4/simple/META-INF/container.xml new file mode 100644 index 0000000..2e8046e --- /dev/null +++ b/tests/files_mscore4/simple/META-INF/container.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/files_mscore4/simple/Thumbnails/thumbnail.png b/tests/files_mscore4/simple/Thumbnails/thumbnail.png new file mode 100644 index 0000000..ecb6834 Binary files /dev/null and b/tests/files_mscore4/simple/Thumbnails/thumbnail.png differ diff --git a/tests/files_mscore4/simple/audiosettings.json b/tests/files_mscore4/simple/audiosettings.json new file mode 100644 index 0000000..96eeea7 --- /dev/null +++ b/tests/files_mscore4/simple/audiosettings.json @@ -0,0 +1,117 @@ +{ + "activeSoundProfile": "MuseScore Basic", + "aux": [ + { + "out": { + "balance": 0, + "fxChain": { + "0": { + "active": true, + "chainOrder": 0, + "resourceMeta": { + "attributes": { + }, + "hasNativeEditorSupport": true, + "id": "Muse Reverb", + "type": "muse_plugin", + "vendor": "Muse" + }, + "unitConfiguration": { + } + } + }, + "volumeDb": 0 + }, + "soloMuteState": { + "mute": false, + "solo": false + } + }, + { + "out": { + "balance": 0, + "fxChain": { + }, + "volumeDb": 0 + }, + "soloMuteState": { + "mute": false, + "solo": false + } + } + ], + "master": { + "balance": 0, + "fxChain": { + }, + "volumeDb": 0 + }, + "tracks": [ + { + "in": { + "resourceMeta": { + "attributes": { + "playbackSetupData": "last.last.last", + "soundFontName": "MS Basic" + }, + "hasNativeEditorSupport": false, + "id": "MS Basic", + "type": "fluid_soundfont", + "vendor": "Fluid" + }, + "unitConfiguration": { + } + }, + "instrumentId": "piano", + "out": { + "auxSends": [ + { + "active": true, + "signalAmount": 0.30000001192092896 + }, + { + "active": true, + "signalAmount": 0.30000001192092896 + } + ], + "balance": 0, + "fxChain": { + }, + "volumeDb": 0 + }, + "partId": "1", + "soloMuteState": { + "mute": false, + "solo": false + } + }, + { + "in": { + "resourceMeta": { + "attributes": { + "playbackSetupData": "last.last.last", + "soundFontName": "MS Basic" + }, + "hasNativeEditorSupport": false, + "id": "MS Basic", + "type": "fluid_soundfont", + "vendor": "Fluid" + }, + "unitConfiguration": { + } + }, + "instrumentId": "metronome", + "out": { + "balance": 0, + "fxChain": { + }, + "volumeDb": 0 + }, + "partId": "999", + "soloMuteState": { + "mute": false, + "solo": false + } + } + ] +} diff --git a/tests/files_mscore4/simple/score_style.mss b/tests/files_mscore4/simple/score_style.mss new file mode 100644 index 0000000..ccc7448 --- /dev/null +++ b/tests/files_mscore4/simple/score_style.mss @@ -0,0 +1,1438 @@ + + + + diff --git a/tests/files_mscore4/simple/simple.mscx b/tests/files_mscore4/simple/simple.mscx new file mode 100644 index 0000000..19a40b8 --- /dev/null +++ b/tests/files_mscore4/simple/simple.mscx @@ -0,0 +1,116 @@ + + + 4.2.0 + eb8d33c + 0 + + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer + + + + + + 4.20 + Linux + + + + + + Title + + + + stdNormal + + + Piano + + Piano + Pno. + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + 100 + 95 + + + 100 + 33 + + + 100 + 50 + + + 100 + 67 + + + 100 + 100 + + + 120 + 67 + + + 120 + 100 + + + + Fluid + + + + + + 10 + 0 + 4294967418 + + 8589934598 + + Title + + + 12884901894 + + Composer + + + + + + 17179869208 + 4 + 4 + + + 21474836505 + measure + 4/4 + + + end + 25769803788 + + + + + + diff --git a/tests/files_mscore4/simple/viewsettings.json b/tests/files_mscore4/simple/viewsettings.json new file mode 100644 index 0000000..692b8ba --- /dev/null +++ b/tests/files_mscore4/simple/viewsettings.json @@ -0,0 +1,5 @@ +{ + "notation": { + "viewMode": "page" + } +} diff --git a/tests/test_score_file_classes.py b/tests/test_score_file_classes.py index 1db6e5c..c64f71a 100644 --- a/tests/test_score_file_classes.py +++ b/tests/test_score_file_classes.py @@ -158,8 +158,21 @@ def test_attribute_viewsettings_path(self) -> None: self.assertTrue(self.container.viewsettings_path.exists()) -class TestMscoreXmlTree(unittest.TestCase): - tree = MscoreXmlTree(helper.get_file("simple.mscx", 3)) +class TestMscoreXmlTreeVersion2(unittest.TestCase): + tree = MscoreXmlTree(helper.get_file("simple.mscz", 2)) + + def test_property_version(self) -> None: + assert self.tree.version == 2.06 + + def test_property_version_major(self) -> None: + assert self.tree.version_major == 2 + + def test_method_get_version(self) -> None: + assert self.tree.get_version() == 2.06 + + +class TestMscoreXmlTreeVersion3(unittest.TestCase): + tree = MscoreXmlTree(helper.get_file("simple.mscz", 3)) def test_property_version(self) -> None: assert self.tree.version == 3.01 @@ -167,18 +180,25 @@ def test_property_version(self) -> None: def test_property_version_major(self) -> None: assert self.tree.version_major == 3 + def test_method_get_version(self) -> None: + assert self.tree.get_version() == 3.01 -class TestClassMscoreXmlTree(unittest.TestCase): - def test_property_version(self): - tree = MscoreXmlTree(helper.get_file("simple.mscx", version=2)) - self.assertEqual(tree.version, 2.06) - self.assertEqual(tree.version_major, 2) - tree = MscoreXmlTree(helper.get_file("simple.mscx", version=3)) - self.assertEqual(tree.version, 3.01) - self.assertEqual(tree.version_major, 3) +class TestMscoreXmlTreeVersion4(unittest.TestCase): + tree = MscoreXmlTree(helper.get_file("simple.mscz", 4)) - def test_method_merge_style(self): + def test_property_version(self) -> None: + assert self.tree.version == 4.2 + + def test_property_version_major(self) -> None: + assert self.tree.version_major == 4 + + def test_method_get_version(self) -> None: + assert self.tree.get_version() == 4.2 + + +class TestClassMscoreXmlTree(unittest.TestCase): + def test_method_merge_style(self) -> None: tree = MscoreXmlTree(helper.get_file("simple.mscx")) styles = """ @@ -207,7 +227,7 @@ def test_method_merge_style(self): self.assertEqual(result[0][0][0].tag, "halign") self.assertEqual(result[0][0][0].text, "center") - def test_method_clean(self): + def test_method_clean(self) -> None: tmp = helper.get_file("clean.mscx", version=3) tree = MscoreXmlTree(tmp) tree.clean() @@ -223,7 +243,7 @@ def test_method_clean(self): self.assertEqual(xml_tree.xpath("//pos"), []) self.assertEqual(xml_tree.xpath("//offset"), []) - def test_method_save(self): + def test_method_save(self) -> None: tmp = helper.get_file("simple.mscx") tree = MscoreXmlTree(tmp) tree.save() @@ -245,7 +265,7 @@ def test_mscz(self): class TestClean(unittest.TestCase): - def _test_clean(self, version=2): + def _test_clean(self, version: int = 2) -> None: tmp = helper.get_file("formats.mscx", version) mscxyz.execute(["clean", tmp]) cleaned = helper.read_file(tmp) @@ -260,7 +280,7 @@ def test_clean(self): self._test_clean(version=2) self._test_clean(version=3) - def _test_clean_add_style(self, version=2): + def _test_clean_add_style(self, version: int = 2) -> None: tmp = helper.get_file("simple.mscx", version) mscxyz.execute(["clean", "--style", helper.get_file("style.mss", version), tmp]) style = helper.read_file(tmp)