diff --git a/.gitignore b/.gitignore index 685efea..eb123e4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ public/ typo3temp/ vendor/ composer.lock +Tests/Testfiles/*.json diff --git a/Classes/Common/XmlDocument.php b/Classes/Common/XmlDocument.php index a305e63..d14b0d8 100644 --- a/Classes/Common/XmlDocument.php +++ b/Classes/Common/XmlDocument.php @@ -2,30 +2,173 @@ namespace Slub\LisztCommon\Common; +use SimpleXMLElement; + class XmlDocument { - protected string $xmlString; + // Set up configuration vars + protected bool $includeLiteralString; + protected bool $includeXmlId; + protected array $splitSymbols; + + // Private helper vars + protected array $convertedArray; + public function __construct(string $xmlString) { $this->xmlString = $xmlString; + + // Deal with xml-reserved symbols + $this->xmlString = str_replace('&', '&', $this->xmlString); + + // Default config + $this->includeLiteralString = false; + $this->includeXmlId = true; + $this->splitSymbols = array(); } - public static function from (string $xmlString): XmlDocument + // Set up config, if needed + + public function setConfig(array $config) + { + $this->includeLiteralString = $config['literalString']; + $this->includeXmlId = $config['xmlId']; + $this->splitSymbols = $config['splitSymbols']; + return $this; + } + + // Functions to set single config aspects + + public function setXmlId(bool $xmlId) + { + $this->includeXmlId = $xmlId; + return $this; + } + + public function setLiteralString(bool $literal) + { + $this->includeLiteralString = $literal; + return $this; + } + + public function setSplitSymbols(array $splitSymbols) + { + $this->splitSymbols = $splitSymbols; + return $this; + } + + public static function from(string $xmlString): XmlDocument { return new XmlDocument($xmlString); } public function toArray(): array { - // add function here - return []; + // Check if array is already converted + if (isset($this->convertedArray)) { + return $this->convertedArray; + } + + $this->convertedArray = []; + $xml = simplexml_load_string($this->xmlString); + $this->convertedArray[$this->getXmlId($xml)] = $this->convert($xml); + return $this->convertedArray; + } + + public function toJson() + { + $result = []; + $xmlArray = $this->toArray(); + + /* + * Convert every key value pair to valid json, + * then decode it, so it stays valid when the whole + * array is encoded + */ + foreach ($xmlArray as $id => $value) { + $result[$id] = json_encode($value); + $result[$id] = json_decode($result[$id], true); + } + return trim(json_encode($result)); + } + + protected function convert(SimpleXMLElement $node): array + { + $result = []; + + // Parse attributes + $attrs = collect($node->attributes())->filter(function ($attrValue) { + return !empty(trim(strval($attrValue))); + })->mapWithKeys(function ($attrValue, $attrName) { + return [$attrName => trim((string) $attrValue)]; + })->toArray(); + + // Merge parsed attributes with result array + if (!empty($attrs)) { + $result = array_merge_recursive($result, ['@attributes' => $attrs]); + } + + // Parse value + $nodeValue = trim((string) $node); + if (!empty($nodeValue)) { + $result['@value'] = $nodeValue; + } + + // Include xml:id attribute + if ($this->includeXmlId) { + $xmlId = $node->attributes('xml', true)->id; + $trimmedXmlId = trim(strval($xmlId)); + if (!empty($trimmedXmlId)) { + $result['@xml:id'] = $trimmedXmlId; + } + } + + // Check if node is a mixed-content element (if literalString is set to true) + + if ($this->includeLiteralString) { + if ($node->getName() == 'p' && $node->count() > 0 && !empty($node)) { + // Add literal string, to store the node order + $literal = str_replace(array("\n", "\r"), '', trim($node->asXML())); + $literal = str_replace('', '', $literal); + $result['@literal'] = $literal; + } + } + + $toParse = collect($node->children())->filter(function ($subject) use ($node, &$result) { + foreach ($this->splitSymbols as $symbol) { + if ($subject->getName() == $symbol) { + $result['@link'] = $this->getXmlId($subject); + $this->convertedArray[$this->getXmlId($subject)] = $this->convert($subject); + return false; + } + } + return true; + }); + + $toParse->each(function ($subject) use (&$result) { + $result = $this->parseChild($subject, $result); + }); + + return $result; + } + + private function parseChild(SimpleXMLElement $child, array $result) + { + $childName = $child->getName(); + $childData = $this->convert($child); + // Always parse child nodes as array + if (!isset($result[$childName])) { + $result[$childName] = []; + } + $result[$childName][] = $childData; + + return $result; } - public function toJson(): string + private function getXmlId(SimpleXMLElement $xml) { - // add function here - return ''; + return strval($xml->attributes('xml', true)->id); } } diff --git a/README.md b/README.md index 4dbccb8..43c9f2c 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,38 @@ This comprises the elasticsearch connection and translation of file formats. You can obtain a Controller with easy access to elasticsearch by inheriting from ClientEnabledController. - use Slub\LisztCommon\Controller\ClientEnabledController; +```php +use Slub\LisztCommon\Controller\ClientEnabledController; - class ActionController extends ClientEnabledController - { +class ActionController extends ClientEnabledController +{ - public function ExampleAction() - { - $this->initializeClient(); - $params = ... - $entity = $this->elasticClient->search($params); - ... + public function ExampleAction() + { + $this->initializeClient(); + $params = ... + $entity = $this->elasticClient->search($params); + ... - } + } - } +} +``` +## Translation between XML and JSON + +You can read in an XML document and translate it to a PHP array or JSON. + +```php +use Slub\LisztCommon\Common\XmlDocument; +... + +$xmlDocument = XmlDocument::from($xmlString); +$array = $xmlDocument->toArray(); +$json = $xmlDocument->toJson(); +``` + +# Maintainer + +If you have any questions or encounter any problems, please do not hesitate to contact me. +- [Matthias Richter](https://github.com/dikastes) diff --git a/Tests/Functional/Common/Fixtures/minimal.json b/Tests/Functional/Common/Fixtures/minimal.json index 8d1c8b6..5d49724 100644 --- a/Tests/Functional/Common/Fixtures/minimal.json +++ b/Tests/Functional/Common/Fixtures/minimal.json @@ -1 +1 @@ - +{"minimal_root":{"@xml:id":"minimal_root","subroot":[{"subsubroot":[{"@value":"entry"}]}]}} diff --git a/Tests/Functional/Common/Fixtures/minimal.xml b/Tests/Functional/Common/Fixtures/minimal.xml index 8d1c8b6..a4bec39 100644 --- a/Tests/Functional/Common/Fixtures/minimal.xml +++ b/Tests/Functional/Common/Fixtures/minimal.xml @@ -1 +1,9 @@ - + + + + + entry + + + + diff --git a/Tests/Testfiles/meitest2.xml b/Tests/Testfiles/meitest2.xml new file mode 100644 index 0000000..819af55 --- /dev/null +++ b/Tests/Testfiles/meitest2.xml @@ -0,0 +1,1553 @@ + + + + + +</titleStmt> +<pubStmt xml:id="pubStmt_e372bebd"> +<respStmt xml:id="respStmt_1f31a778"> +<resp xml:id="resp_43508e00">Publisher</resp> +<corpName role="" xml:id="corpName_cabbbd13"> +<abbr xml:id="abbr_c582e045"/> +<expan xml:id="expan_98e5bbca"/> +<address xml:id="address_a509ae1c"> +<addrLine xml:id="addrLine_e45a19dc"/> +<addrLine xml:id="addrLine_66dcc386"> +<ptr xml:id="ptr_d6e15"/> +</addrLine> +</address> +</corpName> +<persName role="editor" xml:id="persName_448c5973"/> +</respStmt> +<date xml:id="date_1a6ef98a"/> +<availability xml:id="availability_ad5f89a7"> +<useRestrict xml:id="useRestrict_2b3d0fbb"/> +</availability> +</pubStmt> +<seriesStmt xml:id="seriesStmt_af29c337"> +<title xml:id="title_c96f74aa"/> +<identifier type="file_collection" xml:id="identifier_779a4bd0"/> +</seriesStmt> +</fileDesc> +<encodingDesc xml:id="encodingDesc_27f37d4a"> +<appInfo xml:id="appInfo_61a00e9d"> +<application version="2019" xml:id="application_63695818"> +<name xml:id="name_15d8718a">MerMEId</name> +</application> +</appInfo> +<projectDesc xml:id="projectDesc_9e045ab0"/> +<classDecls xml:id="classDecls_1fc0777c"> +<taxonomy xml:id="DcmSourceClassification"> +<category xml:id="DcmContentClass"/> +<category xml:id="DcmPresentationClass"/> +<category xml:id="DcmAuthorityClass"/> +<category xml:id="DcmScoringClass"/> +<category xml:id="DcmStateClass"/> +<category xml:id="DcmCompletenessClass"/> +</taxonomy> +</classDecls> +</encodingDesc> +<workList xml:id="workList_d0ea7b43"> +<work xml:id="work_95d4e38"> +<identifier label="LisztQWV" xml:id="identifier_a818ce07">xyz</identifier> +<title xml:lang="de" xml:id="title_0f87aa14">Die Zelle in Nonnenwerth + +Franz Liszt +Lichnowsky, Felix Maria Vincenz Andreas von +Genast, Emilie + + + + + + + + +English + + + + + + +

Werkinformationen im Überblick

+
+
+ + +Lied +Chamber music +Music for one instrument + + + + +618a +274/1 +N6 +Lied, Version 1, deutsch (1841-42, Druck 1843) + + +

Ach! nun taucht die Klosterzelle

+
+
+Andantino + +ca. 1860 + + + + + +Singst +Kl + + + + + + + + +
+ +618c +274/2 +N6 +Lied, Version 2, deutsch (1858) + + +

Ach! nun taucht die Klosterzelle

+
+
+Andantino + +ca. 1860 + + + + + +Singst +Kl + + + + + + + + + +
+ +618b,c +274/2 +N6 +Lied, Version 3, deutsch (1860) + + +

Ach! nun taucht die Klosterzelle

+
+
+Andantino + +ca. 1860 + + + + + +Singst +Kl + + + + + + + + + +
+ +618b +301/b1 +N6 +Lied, Version 1, französisch (1844) + + +

En ces lieux tout me parle d'elle

+
+
+Andantino + +ca. 1860 + + + + + +Singst +Kl + + + + + + + + + +
+ +618c +301/b2 +N6 +Lied, Version 2, französisch (1845?) + + +

En ces lieux tout me parle d'elle

+
+
+Andantino + +ca. 1860 + + + + + +Singst +Kl + + + + + + + + + +
+ +- +534 +A81a +Kl, Version 1, Elegie (1842) + +Andantino + +ca. 1883 + + + + + + + + + + + + +Kl + + + + + + + + + + + +213 +[274/1] +A81b +Kl, Version 2, Elegie für Pianoforte (1843/44) + +Andantino + +ca. 1883 + + + + + + + + + + + + +Kl + + + + + + + + + + + +64/2 +167 +A81c +Kl, Version 3, Feuilles d'Album Nr. 2 (1849) + +Andantino + +ca. 1883 + + + + + + + + + + + + +Kl + + + + + + + + + + + +213 +534 +A81d +Kl, Version 4, Elegie (1880?) + +Andantino + +ca. 1883 + + + + + + + + + + + + +Kl + + + + + + + + + + + +166g +Kl, Albumblatt, Sérénade (ca. 1840-1849) + + +ca. 1883 + + + + + + + + + + + + +Kl + + + + + + + + + + + +463 +382 +D21 +Vl/Vlc und Kl + +Andantino + +ca. 1883 + + + + + + + + + + + + +Vl (Vc) +Kl + + + + + + + + + + + +213 +534 +Fassung Kl, Nr. 4 + +Andantino + +ca. 1883 + + + + + + + + + + + + +Kl + + + + + + + + + + +
+ + + + +1001248531 + +Arbeitsmanuskript Singstimme und Klavier + + + + + + + + + + + + lines + + + + + + + + + + + +

+ + +

+ + + + + +

+ + + + + +<ref corresp="" xml:id="ref_5be28f47"/> +<heraldry corresp="" xml:id="heraldry_b582b67a"/> +</watermark> +<watermark type="text" xml:id="watermark_1cd46e56"> +<p>Schrift: Liszt (schwarze und sepiafarbene Tinte, Bleistift)</p> +<p>Papier: Hochformat, 267 x 70-75 mm, 20 Notenzeilen, gedruckt</p> +<p>Umfang: 2 Blatt zu 1 Bogen, 4 Seiten Notentext</p> +<p> </p> +</watermark> +</physDesc> +<physDesc xml:id="physDesc_d71f6905"> +<extent quantity="" xml:id="extent_00e9f826"/> +<dimensions xml:id="dimensions_13eb6c21"> +<height quantity="" xml:id="height_23dfe55c"/> +<width quantity="" xml:id="width_9094cc93"/> +<depth quantity="" xml:id="depth_7cb4b6a2"/> +<dim quantity="" xml:id="dim_58222151"/> +</dimensions> +<watermark type="predefined" xml:id="watermark_c498d035"> +<title xml:id="title_46bc9e50"/> +<ref corresp="" xml:id="ref_e3564c79"/> +<heraldry corresp="" xml:id="heraldry_6a0a6bef"/> +</watermark> +<watermark type="text" xml:id="watermark_542eba72"/> +<physMedium xml:id="physMedium_5c48c5b4"/> +<plateNum xml:id="plateNum_1b6825f8"/> +<addDesc xml:id="addDesc_af8e209a"> +<p type="autograph" xml:id="p_8c06ef64"/> +<p type="foreign" xml:id="p_794b3e74"/> +</addDesc> +<supportDesc xml:id="supportDesc_4ebdf339"> +<p type="autograph" xml:id="p_ac32df77"/> +<p type="foreign" xml:id="p_6e0767fb"/> +</supportDesc> +<bindingDesc xml:id="bindingDesc_a0f787c7"> +<binding xml:id="binding_ee2eb8a2"> +<p type="definition" xml:id="p_243c496d"/> +<condition xml:id="condition_ac1a8180"> +<p type="general_description" xml:id="p_14456e61"/> +</condition> +</binding> +</bindingDesc> +<condition xml:id="condition_a062ed9c"> +<p type="general_description" xml:id="p_add7f3bd"/> +</condition> +</physDesc> +<notesStmt xml:id="notesStmt_39d79f12"> +<annot type="source_description" xml:id="annot_e3206303"> +<p>Der ursprüngliche Notentext entspricht der 2. Fassung (vgl. GSA 60/D 63), in dem Liszt zahlreiche Streichungen, Rasuren und Korrekturen vornimmt. Auf [S. 1] streicht er mit Sepia-Tinte und Bleistift das Klaviervorspiel, schreibt über den 1. Takt den Buchstaben A und notiert im 5. Takt Korrekturen mit Bleistift. Auf [S. 3] streicht er mit Sepia-Tinte die ersten beiden Takte und schreibt den Buchstaben B darüber, weiterhin streicht er die letzten beiden Takte der Seite. Auf [S. 4] streicht er bis auf 5 Takte im 3. System alle weiteren Takte, vor die letzten gestrichenen 9 Takte schreibt er den Buchstaben C. Ein weiteres Korrekturblatt liegt der Quelle nicht bei. Im gesamten Notentext finden sich ironische Bemerkungen und kommentierende Erklärungen, die an Emilie Genast adressiert sind. Das Autograph ist am Ende signiert und darunter schreibt Liszt: "an Fräulein Emilie Genast | (die Retterin meiner "ersten" und "letzten" Lieder)" etc.</p> +</annot> +<annot type="links" xml:id="annot_487a8c4a"> +<ptr mimetype="" xml:id="ptr_d6e320_0_0"/> +</annot> +</notesStmt> +<classification xml:id="classification_3b968f34"> +<termList xml:id="termList_66d63f0a"> +<term xml:id="term_f74914ef">Manuscript</term> +<term xml:id="term_accb3073">Notated music</term> +<term xml:id="term_82749641">Autograph</term> +<term xml:id="term_b00246fe">Score</term> +<term xml:id="term_3fdb9c0b">Draft</term> +<term xml:id="term_3a50782f">Complete</term> +</termList> +</classification> +<itemList xml:id="itemList_cf8c7dd4"> +<item xml:id="item_6167e040" label="GSA 60/D 94"> +<identifier xml:id="identifier_ccabbfaa"/> +<physDesc xml:id="physDesc_e3a6b5fb"> +<m:titlePage xmlns:m="http://www.music-encoding.org/ns/mei" label="Kopftitel" xml:id="titlePage_2a7bb7db"> +<p>Nonnenwerth (ein nichts werthes | Erinnerungs Blatt!)</p> +</m:titlePage> +</physDesc> +<physLoc xml:id="physLoc_6e8d94a9"> +<repository xml:id="repository_b9d0a9d7"> +<corpName xml:id="corpName_33bf4452" role="">Goethe- und Schiller-Archiv</corpName> +<geogName xml:id="geogName_c5298d47" role="">Weimar</geogName> +<identifier auth="RISM" auth.uri="http://www.rism.info" xml:id="identifier_739e4a32">D-WRgs</identifier> +</repository> +<identifier xml:id="identifier_c762ae61">GSA 60/D 94</identifier> +</physLoc> +<notesStmt xml:id="notesStmt_a069fa11"> +<annot type="source_description" xml:id="annot_c9731770"/> +<annot type="links" xml:id="annot_d493f6c3"> +<ptr target="https://ores.klassik-stiftung.de/ords/f?p=401:2:::::P2_ID:191640" mimetype="" label="Digitalisat" xml:id="ptr_d6e396_0"/> +<ptr target="https://ores.klassik-stiftung.de/ords/rest_api/iiif/digi_gsa/191640/manifest" mimetype="" label="Digitalisat IIIF" xml:id="ptr_d6e320_1_0"/> +</annot> +</notesStmt> +<componentList xml:id="componentList_2000e4b4"/> +</item> +</itemList> +<componentList xml:id="componentList_92fc6848"/> +<relationList xml:id="relationList_cdf8cf6b"> +<relation rel="isEmbodimentOf" xml:id="relation_80253837" target="#expression_95d4e55"/> +</relationList> +</manifestation> +<manifestation xml:id="manifestation_4c9abab8"> +<identifier xml:id="identifier_4ed9a23e"/> +<titleStmt xml:id="titleStmt_03c9a3ce"> +<title xml:id="title_77948f54">Arbeitsmanuskript Singstimme und Klavier + + + + + + + + + + + + lines + + + + + + + + + + + +

+ + +

+ + + + + +

+ + + + + +<ref corresp="" xml:id="ref_4bcaa7b2"/> +<heraldry corresp="" xml:id="heraldry_c85808ec"/> +</watermark> +<watermark type="text" xml:id="watermark_b00b073d"> +<p>Schrift: Liszt (schwarze und sepiafarbene Tinte, Bleistift)</p> +<p>Papier: Hochformat, 267 x 70-75 mm, 20 Notenzeilen, gedruckt</p> +<p>Umfang: 2 Blatt zu 1 Bogen, 4 Seiten Notentext</p> +<p> </p> +</watermark> +</physDesc> +<physDesc xml:id="physDesc_05839141"> +<extent quantity="" xml:id="extent_2129a8f5"/> +<dimensions xml:id="dimensions_65ac5aaf"> +<height quantity="" xml:id="height_cc8aa55c"/> +<width quantity="" xml:id="width_36644e33"/> +<depth quantity="" xml:id="depth_e9752bdc"/> +<dim quantity="" xml:id="dim_639dae51"/> +</dimensions> +<watermark type="predefined" xml:id="watermark_fa1348f5"> +<title xml:id="title_28acf1d6"/> +<ref corresp="" xml:id="ref_73c4e286"/> +<heraldry corresp="" xml:id="heraldry_c823228b"/> +</watermark> +<watermark type="text" xml:id="watermark_c1094a63"/> +<physMedium xml:id="physMedium_67870f66"/> +<plateNum xml:id="plateNum_931a4a76"/> +<addDesc xml:id="addDesc_d614b2e0"> +<p type="autograph" xml:id="p_f4f73fa5"/> +<p type="foreign" xml:id="p_d13d8a25"/> +</addDesc> +<supportDesc xml:id="supportDesc_431cdcf6"> +<p type="autograph" xml:id="p_f5c6d6b8"/> +<p type="foreign" xml:id="p_b7b085e9"/> +</supportDesc> +<bindingDesc xml:id="bindingDesc_e8993660"> +<binding xml:id="binding_1a12e094"> +<p type="definition" xml:id="p_0a4fdef5"/> +<condition xml:id="condition_eecd5a30"> +<p type="general_description" xml:id="p_1e128811"/> +</condition> +</binding> +</bindingDesc> +<condition xml:id="condition_235f9332"> +<p type="general_description" xml:id="p_078bdad9"/> +</condition> +</physDesc> +<notesStmt xml:id="notesStmt_5b9d482d"> +<annot type="source_description" xml:id="annot_cfee619a"/> +<annot type="links" xml:id="annot_37e5fe99"> +<ptr mimetype="" xml:id="ptr_8d3eaca2"/> +</annot> +</notesStmt> +<classification xml:id="classification_c7c26e37"> +<termList xml:id="termList_ceda6b25"> +<term xml:id="term_0713d123">Manuscript</term> +<term xml:id="term_c7a0d011">Notated music</term> +<term xml:id="term_db3d23ef">Score</term> +<term xml:id="term_14db0677">Complete</term> +<term>Partly autograph</term> +<term>Fair copy</term> +</termList> +</classification> +<itemList xml:id="itemList_57c67e7b"> +<item label="GSA 60/D 63, Abschrift 1" xml:id="item_59734855"> +<identifier xml:id="identifier_00c3ea58"/> +<physDesc xml:id="physDesc_df611d1a"/> +<physLoc xml:id="physLoc_86510ef6"> +<repository xml:id="repository_0e73c3ce"> +<corpName role="" xml:id="corpName_a990bf54">Goethe- und Schiller-Archiv</corpName> +<geogName role="" xml:id="geogName_916f74b4">Weimar</geogName> +<identifier auth="RISM" auth.uri="http://www.rism.info" xml:id="identifier_a88317ad">D-WRgs</identifier> +</repository> +<identifier xml:id="identifier_d9b31f2e">GSA 60/D 63</identifier> +</physLoc> +<history xml:id="history_fc8e409e"> +<acquisition xml:id="acquisition_cc1c76b4"> +<date xml:id="date_96628262"/> +</acquisition> +</history> +<notesStmt xml:id="notesStmt_d87c6654"> +<annot type="source_description" xml:id="annot_308bc8b8"/> +<annot type="links" xml:id="annot_cb0297d0"> +<ptr target="https://ores.klassik-stiftung.de/ords/f?p=401:2:::::P2_ID:191540" mimetype="" label="Digitalisat" xml:id="ptr_36fa1bb1"/> +<ptr target="https://ores.klassik-stiftung.de/ords/rest_api/iiif/digi_gsa/191540/manifest" mimetype="" label="Digitalisat IIIF" xml:id="ptr_3ebd14c2"/> +</annot> +</notesStmt> +<componentList xml:id="componentList_dc509a8f"/> +</item> +</itemList> +<componentList xml:id="componentList_0e152fd9"/> +<relationList xml:id="relationList_14c7f567"> +<relation rel="isEmbodimentOf" target="#expression_95d4e55" xml:id="relation_3b5c4ca1"/> +</relationList> +</manifestation> +<manifestation xml:id="manifestation_bf7baba8"> +<identifier xml:id="identifier_afc9a654"/> +<titleStmt xml:id="titleStmt_fea567e1"> +<title xml:id="title_6f1983f0">Arbeitsmanuskript Singstimme und Klavier + + + + + + + + + + + + lines + + + + + + + + + + + +

+ + +

+ + + + + +

+ + + + + +<ref corresp="" xml:id="ref_1dc8237d"/> +<heraldry corresp="" xml:id="heraldry_f735eef7"/> +</watermark> +<watermark type="text" xml:id="watermark_27690a44"> +<p>Schrift: Liszt (schwarze und sepiafarbene Tinte, Bleistift)</p> +<p>Papier: Hochformat, 267 x 70-75 mm, 20 Notenzeilen, gedruckt</p> +<p>Umfang: 2 Blatt zu 1 Bogen, 4 Seiten Notentext</p> +<p> </p> +</watermark> +</physDesc> +<physDesc xml:id="physDesc_f5014d6a"> +<extent quantity="" xml:id="extent_34522be6"/> +<dimensions xml:id="dimensions_e48878e9"> +<height quantity="" xml:id="height_34b59b8b"/> +<width quantity="" xml:id="width_d34551e4"/> +<depth quantity="" xml:id="depth_70cb7feb"/> +<dim quantity="" xml:id="dim_3c50af21"/> +</dimensions> +<watermark type="predefined" xml:id="watermark_c3a2c9ea"> +<title xml:id="title_5207f9d5"/> +<ref corresp="" xml:id="ref_c557ba2a"/> +<heraldry corresp="" xml:id="heraldry_d6b5e07e"/> +</watermark> +<watermark type="text" xml:id="watermark_6da1d0c5"/> +<physMedium xml:id="physMedium_809d7df4"/> +<plateNum xml:id="plateNum_bde35c35"/> +<addDesc xml:id="addDesc_bc1c9809"> +<p type="autograph" xml:id="p_aa3ed8bb"/> +<p type="foreign" xml:id="p_4f6aea33"/> +</addDesc> +<supportDesc xml:id="supportDesc_7607a3f5"> +<p type="autograph" xml:id="p_4eb9bdf4"/> +<p type="foreign" xml:id="p_c79fd7c9"/> +</supportDesc> +<bindingDesc xml:id="bindingDesc_b9eaddef"> +<binding xml:id="binding_855ec538"> +<p type="definition" xml:id="p_450f147e"/> +<condition xml:id="condition_9f0c03b2"> +<p type="general_description" xml:id="p_af4c8425"/> +</condition> +</binding> +</bindingDesc> +<condition xml:id="condition_50463c5a"> +<p type="general_description" xml:id="p_cbd0794c"/> +</condition> +</physDesc> +<notesStmt xml:id="notesStmt_a82498ea"> +<annot type="source_description" xml:id="annot_bbfcda4d"/> +<annot type="links" xml:id="annot_4384032d"> +<ptr mimetype="" xml:id="ptr_736850cd"/> +</annot> +</notesStmt> +<classification xml:id="classification_666eb103"> +<termList xml:id="termList_c6238ffb"> +<term xml:id="term_189da065">Manuscript</term> +<term xml:id="term_74c77ac2">Notated music</term> +<term xml:id="term_2732debb">Score</term> +<term xml:id="term_a7251117">Complete</term> +<term xml:id="term_f74df8b6">Partly autograph</term> +<term xml:id="term_061f9c67">Fair copy</term> +</termList> +</classification> +<itemList xml:id="itemList_1fcc892a"> +<item label="GSA 60/D 63, Abschrift 2" xml:id="item_88b4e7c5"> +<identifier xml:id="identifier_b0b7be15"/> +<physDesc xml:id="physDesc_37e34adb"/> +<physLoc xml:id="physLoc_6d62c299"> +<repository xml:id="repository_fe09c6ff"> +<corpName role="" xml:id="corpName_6a15d1ee">Goethe- und Schiller-Archiv</corpName> +<geogName role="" xml:id="geogName_20bdb566">Weimar</geogName> +<identifier auth="RISM" auth.uri="http://www.rism.info" xml:id="identifier_fd6c8201">D-WRgs</identifier> +</repository> +<identifier xml:id="identifier_ef092d92">GSA 60/D 63</identifier> +</physLoc> +<history xml:id="history_9aaec371"> +<acquisition xml:id="acquisition_8bfd3524"> +<date xml:id="date_88d2892b"/> +</acquisition> +</history> +<notesStmt xml:id="notesStmt_8709d9f0"> +<annot type="source_description" xml:id="annot_7c18584a"/> +<annot type="links" xml:id="annot_fe4c1769"> +<ptr target="https://ores.klassik-stiftung.de/ords/f?p=401:2:::::P2_ID:191540" mimetype="" label="Digitalisat" xml:id="ptr_56e5f8b4"/> +<ptr target="https://ores.klassik-stiftung.de/ords/rest_api/iiif/digi_gsa/191540/manifest" mimetype="" label="Digitalisat IIIF" xml:id="ptr_eed6fc8b"/> +</annot> +</notesStmt> +<componentList xml:id="componentList_7c9f6923"/> +</item> +</itemList> +<componentList xml:id="componentList_99c2c8df"/> +<relationList xml:id="relationList_81db8496"> +<relation rel="isEmbodimentOf" target="#expression_95d4e55" xml:id="relation_6e675c20"/> +</relationList> +</manifestation> +<manifestation xml:id="manifestation_858dacfd" label="Arbeitsmanuskript und Abschrift"> +<identifier xml:id="identifier_f91bda9c"/> +<titleStmt xml:id="titleStmt_3aa74554"> +<title xml:id="title_6c037b8e">Version Vl/Vc und Kl + + + +

Das Manuskript besteht aus einem Autograph von Liszt und einer Abschrift von [Weber 125165311], die Liszt miteinander verbunden hat, und die zusammen die Komposition ergeben. Die S. 1-3 stammen von Liszts Hand, ebenso die S. 7-12. Die S. 4-6 und 13-14 sind Abschriften von [Weber 125165311]. Das Manuskript enthält die gesamte Komposition. Die Vl notiert Liszt mit roter Tinte, Vlc und Kl mit schwarzer Tinte. Gelegentlich finden sich einzelne Zeichen auch in den übrigen Stimmen, die Liszt mit roter Tinte verbessert hat. Auf S. 7 hat Liszt das untere System überklebt und neu komponiert, während er die Überklebung auf S. 12 aus [Webers 1251653111] Abschrift herausgeschnitten hat.

+ + + + + + + +Notated music +Manuscript +Partly autograph +Score +Draft +Complete + + + + +1001248448 + + +

Die Zelle in Nonnenwerth.

+
+ + +Wilhelm Weber + + +
+ + + + + + + lines + + + + + + + + + + + +

+ + +

+ + + + + +

+ + + + + +<ref corresp="" xml:id="ref_60b32b19"/> +<heraldry corresp="" xml:id="heraldry_a0abcb5d"/> +</watermark> +<watermark type="text" xml:id="watermark_760521eb"> +<p>Schrift: Liszt (schwarze und rote Tinte), Wilhelm Weber (schwarze Tinte)</p> +<p>Umfang: 7 Blatt zu 3 Bogen und eingeklebtes Einzelblatt, 14 Seiten Notentext, paginiert, Fadenbindung</p> +<p>Papier: Hochformat, 353 x 267 mm, 12 Notenzeilen, gedruckt (B. und H. Nr. 1 C.); Überklebung S. 7a, Querformat, 161 x 265 mm, 6 Notenzeilen, gedruckt; Überklebung S. 12a, Querformat, 126-131 x 264 mm, 5 Notenzeilen, gedruckt</p> +<p> </p> +</watermark> +</physDesc> +<physLoc xml:id="physLoc_ea6da757"> +<repository xml:id="repository_af775fb5"> +<corpName role="" xml:id="corpName_e397e190">Goethe- und Schiller-Archiv</corpName> +<geogName role="" xml:id="geogName_a2fdf2c0">Weimar</geogName> +<identifier auth="RISM" auth.uri="http://www.rism.info" xml:id="identifier_0accbf0f">D-WRgs</identifier> +</repository> +<identifier xml:id="identifier_97376b71">GSA 60/X 1,1</identifier> +</physLoc> +<notesStmt xml:id="notesStmt_ea16edac"> +<annot type="source_description" xml:id="annot_87110430"/> +<annot type="links" xml:id="annot_feaeb22a"> +<ptr target="https://ores.klassik-stiftung.de/ords/f?p=401:2:::::P2_ID:209688" mimetype="" label="Digitalisat" xml:id="ptr_d6e396_1"/> +<ptr target="https://ores.klassik-stiftung.de/ords/rest_api/iiif/digi_gsa/209688/manifest" mimetype="" label="Digitalisat IIIF" xml:id="ptr_d6e320_1_1"/> +</annot> +</notesStmt> +<componentList xml:id="componentList_9217d66e"/> +</item> +</itemList> +<componentList xml:id="componentList_c17f9296"/> +<relationList xml:id="relationList_b08066d1"> +<relation rel="isEmbodimentOf" xml:id="relation_88fb9040" target="#expression_c1dd8547"/> +</relationList> +</manifestation> +<manifestation xml:id="manifestation_86c6775b" label="Reinschrift"> +<identifier xml:id="identifier_c852e83f"/> +<titleStmt xml:id="titleStmt_ffb2c2e9"> +<title xml:id="title_ba25e6e8">Version Vl/Vc und Kl + + + +Notated music +Manuscript +Partly autograph +Parts +Fair copy +Complete + + + + +1001248558 + + +

Nonnenwerth

+ + + +Wilhelm Weber + + + +

Quellentyp: Abschrift

+

Schrift: Wilhelm Weber (schwarze Tinte), zS (roter und blauer Stift)

+

Umfang: 9 Blatt zu 3 Bogen und 3 Einzelblatt, 1 Titelseite, 17 Seiten Notentext, paginiert und foliiert, Fadenbindung

+

Papier: Hochformat, 350 x 270-273 mm, 10 Notenzeilen, gedruckt (B. und H. Nr. 18. C)

+
+
+ + +Goethe- und Schiller-Archiv +Weimar +D-WRgs + +GSA 60/X 1 + + + + + + + + + + + +Liszt-Museum + + + + + + + +

Eine sehr saubere Abschrift, die keine Eintragungen Liszts enthält. Der Klavierpart ist der letzten Fassung der Elegie ("Nonnenwerth") für Kl entlehnt, stimmt jedoch nicht durchgängig mit der Kl-Fassung überein.

+
+ + + + +
+ + + + + +
+
+ + + + +
+ + + +Version Vl/Vc und Kl + + + +Notated music +Manuscript +Partly autograph +Parts +Fair copy +Complete + + + + +1001248553 + + +

"Nonnenwerth" | Elegie.

+
+ + + + +180 (height in mm) +270 (height in mm) + + lines + + + + + + + + + + + + + + + + + +Wilhelm Weber + + + +

Schrift: Wilhelm Weber (schwarze Tinte), Liszt [?] (Bleistift)

+

Umfang, vl: 2 Blatt zu 1 Bogen, 4 Seiten Notentext, paginiert und foliiert

+

Umfang, vlc: 2 Blatt zu 1 Bogen, 4 Seiten Notentext, paginiert und foliiert

+

Papier: Querformat, 180 x 270 mm, 8 Notenzeilen, gedruckt (B. und H. Nr. 19. C.)

+

Eine sehr saubere Stimme mit minimalen Korrekturen (Bleistift), vielleicht von Liszt.

+

+

+
+
+ + +Goethe- und Schiller-Archiv +Weimar +D-WRgs + +GSA 60/X 1 + + + + + + + + + + +Weimar +Liszt-Museum + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +Version Vl/Vc und Kl + + + +Notated music +Manuscript +Partly autograph +Parts +Fair copy +Complete + + + + +1001248558 + + +

"Die Zelle in Nonnenwerth" v. F. Liszt.

+
+ + + + + + + lines + + + + + + + + + + + + + + + + + +Wilhelm Weber + + + +

Saubere Abschrift von der Hand [Wilhelm Webers 1251653111]. Es sind keine Eintragungen Liszts vorhanden. Das Manuskript schließt mit dem Auftrag "Bitte die erste Abschrift Herrn Dr. F. Liszt | wieder zu verabreichen".

+

Datierung: W. Weber. | d. 21./8/83.

+

Schrift: Wilhelm Weber (schwarze Tinte)

+

Umfang: 1 Blatt, 2 Seiten Notentext, paginiert und foliiert

+

Papier: Hochformat, 333 x 249 mm, 12 Notenzeilen, handrastriert

+
+
+ + +Goethe- und Schiller-Archiv +Weimar +D-WRgs + +GSA 60/X 1 + + + + + + + + + + + + + +
+
+ + + + +
+ + + +Kl-Fassung Nr. 2, 3. Auflage (Hofmeister) + + +Friedrich Hofmeister +Leipzig + + + + +

Die Zelle in Nonnenwerth. | ELEGIE | für | Pianoforte | von | FRANZ LISZT. | 3. Auflage. Pr. 17 1/2 Neugr. | Mk. 1,75. | Eigenthum des Verlegers. | LEIPZIG, FRIEDRICH HOFMEISTER. | 6882. | Anst. f. Musikaliendruck v. Carl Schulze, Leipzig

+
+ + + + + + + + + +<ref corresp="" xml:id="ref_3bc2323a"/> +<heraldry corresp="" xml:id="heraldry_4234ac56"/> +</watermark> +<watermark type="text" xml:id="watermark_c1325a9c"/> +<physMedium xml:id="physMedium_b3089476"/> +<plateNum xml:id="plateNum_1fa6b880"/> +<addDesc xml:id="addDesc_51150606"> +<p type="autograph" xml:id="p_cea22d4e"/> +<p type="foreign" xml:id="p_b36d812b"/> +</addDesc> +<supportDesc xml:id="supportDesc_2f396b64"> +<p type="autograph" xml:id="p_5599c966"> +<p>Druck: Titelblatt, Leerseite, Notentext S. [3]-11, Leerseite</p> +</p> +<p type="foreign" xml:id="p_0e2b7d84"/> +</supportDesc> +<bindingDesc xml:id="bindingDesc_b4c3ea2f"> +<binding xml:id="binding_525253d6"> +<p type="definition" xml:id="p_aa0fdeae"/> +<condition xml:id="condition_58160034"> +<p type="general_description" xml:id="p_82677c8b"/> +</condition> +</binding> +</bindingDesc> +<condition xml:id="condition_eff18845"> +<p type="general_description" xml:id="p_c1efe538"/> +</condition> +</physDesc> +<classification xml:id="classification_6d8052bd"> +<termList xml:id="termList_8dbe5081"> +<term xml:id="term_e41b189c">Notated music</term> +<term xml:id="term_dc2d4150">Manuscript</term> +<term xml:id="term_b6a01fff">Parts</term> +<term xml:id="term_a4712b06">Fair copy</term> +<term xml:id="term_7e6bd6ea">Complete</term> +<term xml:id="term_9ba77697">Autograph</term> +</termList> +</classification> +<itemList xml:id="itemList_c5330f9e"> +<item label="Druck HAAB" xml:id="item_ccbcca3f"> +<identifier xml:id="identifier_ffb6760c"/> +<physDesc xml:id="physDesc_d1b2dffa"> +<dimensions xml:id="dimensions_e70ffadb"> +<locusGrp xml:id="locusGrp_15ef6b75"> +<locus xml:id="locus_18f1e3eb"/> +</locusGrp> +<dim quantity="" xml:id="dim_50da21e7"/> +<term xml:id="term_6e135409"/> +<extent quantity="" xml:id="extent_af2b897a"> lines</extent> +<dimensions type="format" xml:id="dimensions_0c5b626a"> +<height quantity="" unit="cm" xml:id="height_5ab427cf"/> +<width quantity="" unit="cm" xml:id="width_626f8702"/> +</dimensions> +<dimensions type="rastral_mirror" xml:id="dimensions_13042e3f"> +<height quantity="" unit="cm" xml:id="height_a4862b35"/> +<width quantity="" unit="cm" xml:id="width_020ac1b2"/> +</dimensions> +</dimensions> +<condition xml:id="condition_64a66029"/> +<dimensions xml:id="dimensions_11c81882"> +<height quantity="" xml:id="height_a7a0d167"/> +<width quantity="" xml:id="width_660fe763"/> +<depth quantity="" xml:id="depth_1832ae56"/> +<dim quantity="" xml:id="dim_bdce70f9"/> +</dimensions> +<handList xml:id="handList_9a26770a"> +<hand xmlns:xl="http://www.w3.org/1999/xlink" xmlns:xs="http://www.w3.org/2001/XMLSchema" medium="" type="main" xml:id="hand_6e361de5"/> +</handList> +<physMedium xml:id="physMedium_53071ea5"/> +</physDesc> +<physDesc type="paper_type" xml:id="physDesc_70c9d62c"> +<dimensions xml:id="dimensions_ff74f50b"> +<locusGrp xml:id="locusGrp_0d4eba0e"> +<locus xml:id="locus_d0aeed80"/> +</locusGrp> +<term xml:id="term_16254a14"/> +<extent quantity="" xml:id="extent_6750e394"> lines</extent> +<dimensions type="format" xml:id="dimensions_76f0e66d"> +<height quantity="" unit="cm" xml:id="height_002449c7"/> +<width quantity="" unit="cm" xml:id="width_7e4c4883"/> +</dimensions> +<dimensions type="rastral_mirror" xml:id="dimensions_0a6b64db"> +<height quantity="" unit="cm" xml:id="height_5e101fce"/> +<width quantity="" unit="cm" xml:id="width_f9f5f41c"/> +</dimensions> +</dimensions> +<supportDesc xml:id="supportDesc_8937a8d8"> +<support type="paper_quality" xml:id="support_08d9f104"> +<p xml:id="p_b0afd61c"/> +</support> +<condition xml:id="condition_9a8ab154"> +<p xml:id="p_f2ce4dd7"/> +</condition> +</supportDesc> +<bindingDesc xml:id="bindingDesc_d4bb6e20"> +<binding xml:id="binding_41cff9a4"> +<condition xml:id="condition_c8f9cb65"> +<p type="general_description" xml:id="p_f5620bef"/> +</condition> +</binding> +</bindingDesc> +<watermark type="predefined" xml:id="watermark_f9bfb667"> +<title xml:id="title_dbc66e58"/> +<ref corresp="" xml:id="ref_0fe080c8"/> +<heraldry corresp="" xml:id="heraldry_992d6ab6"/> +</watermark> +<watermark type="text" xml:id="watermark_3b0cc858"/> +</physDesc> +<physLoc xml:id="physLoc_0cd78967"> +<repository xml:id="repository_b44670db"> +<corpName role="" xml:id="corpName_3d3b5960">Anna Amalia Bibliothek</corpName> +<geogName role="" xml:id="geogName_c6f37877">Weimar</geogName> +<identifier auth="RISM" auth.uri="http://www.rism.info" xml:id="identifier_0cb87a92">D-WRz</identifier> +</repository> +<identifier xml:id="identifier_438fb6cd"> L 1761</identifier> +</physLoc> +<notesStmt xml:id="notesStmt_d5f5bd60"> +<annot type="source_description" xml:id="annot_9abaf1f1"> +<p>Enthält wenige Eintragungen auf dem Titelblatt. </p> +</annot> +<annot type="links" xml:id="annot_1075fddf"> +<ptr mimetype="" target="https://haab-digital.klassik-stiftung.de/viewer/!metadata/4099010198/4/-/" label="Digitalisat" xml:id="ptr_d29eac01"/> +<ptr target="https://haab-digital.klassik-stiftung.de/viewer/api/v1/records/4099010198/manifest/" mimetype="" label="Digitalisat IIIF" xml:id="ptr_994dd013"/> +</annot> +</notesStmt> +<componentList xml:id="componentList_ca721984"> +<item label="Andantino" xml:id="item_22dad3f1"> +<physDesc xml:id="physDesc_e23d418d"/> +</item> +</componentList> +</item> +</itemList> +<componentList xml:id="componentList_4a284e0f"/> +<relationList xml:id="relationList_354dc321"> +<relation rel="isEmbodimentOf" target="#expression_f0a9bb34" xml:id="relation_6baae894"/> +</relationList> +</manifestation> +<manifestation xml:id="manifestation_8dfc3954" label="Arbeitsmanuskript"> +<identifier xml:id="identifier_f82caa39"/> +<titleStmt xml:id="titleStmt_db9cef88"> +<title xml:id="title_3fae984a">Kl, Version Nr. 4 +
+ + +Notated music +Manuscript +Parts +Fair copy +Complete +Autograph + + + + +1001248443 + + +

"Die Zelle in | Nonnenwerth" | (nach einem Gedichte des Fürsten | Felix Lichnowsky) | für Pianoforte | von | Franz Liszt. | (Letzte neu veränderte Ausgabe)

+
+ +

D. Zelle in Nonnenwerth | Elegie | für Pianoforte | v. Fr. Liszt

+
+ +

Die Zelle in Nonnenwerth – | Elegie, für Pianoforte | von F. Liszt (nach einem Gedicht | des Fürsten Felix | Lichnowsky)

+
+ + + + + + + lines + + + + + + + + + + + + + + + + + + + +

Schrift: Liszt (schwarze und bordeaux-rote Tinte, blauer und roter Stift)

+

Umfang: 6 Blatt zu 1 Bogen und 4 Einzelblatt, 2 Titelseiten, 8 Seiten Notentext, 2 Leerseiten, paginiert

+

Papier: Hochformat, 354 x 270 mm, 12 Notenzeilen, gedruckt (B. und H. Nr. 1. C.); Überklebung S. 2a, Querformat, 59-68 x 254 mm, 2 Notenzeilen, gedruckt, oben abgeschnitten

+

+
+
+ + +Goethe- und Schiller-Archiv +Weimar +D-WRgs + +GSA 60/U 80 + + + +

Die Version stimmt zum größten Teil mit der Druckfassung der Neuen Liszt-Ausgabe (Bd. I/17, Klavierversionen eigener Werke III, S. 117-121) überein. Erneut ein schönes Beispiel, an dem die unterschiedlichen Arbeitsschritte deutlich werden (Revisionen).

+
+ + + + +
+ + + + + +
+
+ + + + + + + + + + + + +

+ + + + +mermeid + + +

file created with MerMEId

+
+
+ + + + + +

+ + + + + + + +

+ + + + + + + +

+ + + + + + + +

+ + + + + + + +

+ + + + + + + +

+ + + + + + diff --git a/Tests/Unit/Common/XmlDocumentTest.php b/Tests/Unit/Common/XmlDocumentTest.php index 5fd1497..c89b43f 100644 --- a/Tests/Unit/Common/XmlDocumentTest.php +++ b/Tests/Unit/Common/XmlDocumentTest.php @@ -11,12 +11,14 @@ final class XmlDocumentTest extends UnitTestCase { private XmlDocument $subject; + private $xmlString; protected function setUp(): void { parent::setUp(); - $this->subject = new XmlDocument(''); + $this->xmlString = file_get_contents('Tests/Testfiles/meitest2.xml'); + $this->subject = XmlDocument::from($this->xmlString); } /** @@ -24,7 +26,7 @@ protected function setUp(): void */ public function returnsArray(): void { - self::assertSame([], $this->subject->toArray()); + self::assertIsArray($this->subject->toArray()); } /** @@ -32,6 +34,117 @@ public function returnsArray(): void */ public function returnsJson(): void { - self::assertSame('', $this->subject->toJson()); + self::assertJson($this->subject->toJson()); + } + + /** + * @test + */ + public function xmlStringNotEmpty(): void + { + self::assertNotSame('', $this->xmlString); + } + + /** + * @test + */ + public function testFluidInterface() + { + self::assertJson($this->subject->setXmlId(true)->toJson()); + } + + /** + * @test + */ + public function testXmlIdIsOmited() + { + self::assertFalse(str_contains($this->subject->setXmlId(false)->toJson(), '@xmlId')); + } + + /** + * @test + */ + public function xmlIdIsIncluded() + { + self::assertStringContainsString('@xml:id', $this->subject->setXmlId(true)->toJson()); + } + + /** + * @test + */ + public function testMixedContentIsIncluded() + { + $mixedContentString = '

I am mixed content

'; + $subject = XmlDocument::from($mixedContentString); + $subject->setLiteralString(true); + $expected = '"@literal":"

I am mixed <\/b> content <\/p>"'; + self::assertStringContainsString($expected, $subject->toJson()); + } + + /** + * @test + */ + public function testMixedContentIsNotIncluded() + { + $this->subject->setLiteralString(false); + self::assertFalse(str_contains($this->subject->toJson(), '@literal')); + } + + /** + * @test + */ + public function testConvertedAttribute() + { + $subject = XmlDocument::from(''); + + $expected = '{"item_test":{"@attributes":{"id":"1","name":"ExampleItem"},"@xml:id":"item_test"}}'; + + self::assertSame($expected, $subject->toJson()); + } + + /** + * @test + */ + public function testPlainText() + { + $subject = XmlDocument::from('

I am plain text

'); + $expected = '{"testid": { + "@value": "I am plain text", + "@xml:id": "testid" } + }'; + + self::assertJsonStringEqualsJsonString($expected, $subject->toJson()); + } + + /** + * @test + */ + public function testSplitSymbols() + { + $xmlString = ' + + + + + + + + + + + + + + + + + + '; + + $expected = '{"TC-01":{"@xml:id":"TC-01","measure":[{"@attributes":{"n":"1"},"staff":[{"@attributes":{"n":"1"},"layer":[{"@attributes":{"n":"1"},"note":[{"@attributes":{"pname":"e","oct":"4","dur":"4"},"@xml:id":"N2"}]}]}]}]},"mei_head":{"@xml:id":"mei_head","music":[{"body":[{"@link":"TC-01"}]}]}}'; + + $subject = XmlDocument::from($xmlString)->setSplitSymbols(['mdiv']); + + self::assertJsonStringEqualsJsonString($expected, $subject->toJson()); } } diff --git a/composer.json b/composer.json index 482841e..510f44c 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ }, "require-dev": { "phpstan/phpstan": "^1", - "phpunit/phpunit": "^9", + "phpunit/phpunit": "^9.4", "typo3/testing-framework": "^7" }, "autoload": {