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)