From 27aaa985033fde42427a28a6e682f37dfd21e22c Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Fri, 17 May 2024 06:23:33 -0400 Subject: [PATCH] Implement additional content validation * graceGrp and beam should not be empty --- include/vrv/iopae.h | 11 +++++++++- src/iopae.cpp | 49 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/include/vrv/iopae.h b/include/vrv/iopae.h index b02f0620ad..b007c4cb34 100644 --- a/include/vrv/iopae.h +++ b/include/vrv/iopae.h @@ -496,6 +496,8 @@ namespace pae { bool IsSpace(); /** Return true is the token has to be ignore during parsing */ bool IsVoid(); + /** Set the object as being inserted in the MEI tree */ + void SetInTree(); /* Helper to the a lowercase version of the Object classname (if any) */ std::string GetName(); @@ -504,6 +506,8 @@ namespace pae { char m_char; /** The Object to be added to the tree */ Object *m_object; + /** The object added to the tree */ + Object *m_treeObject; /** the input char preserved for debugging purposes */ char m_inputChar; /** the position in the original input string for debuggin purposes */ @@ -654,7 +658,6 @@ class PAEInput : public Input { /** * Some additional checked to be performed one the MEI tree has been build. - * Unimplemented */ bool CheckContentPostBuild(); @@ -663,6 +666,12 @@ class PAEInput : public Input { */ void RemoveContainerToken(Object *object); + /** + * Return the token corresponding to an object in the tree. + * Return NULL if not found. + */ + pae::Token *GetTokenForTreeObject(Object *object); + /** * @name Some logging methods specific to the PAE parser */ diff --git a/src/iopae.cpp b/src/iopae.cpp index 005f1ce0d3..8ae1261b98 100644 --- a/src/iopae.cpp +++ b/src/iopae.cpp @@ -2296,7 +2296,8 @@ enum { ERR_062_LIGATURE_NOTE_AFTER, ERR_063_LIGATURE_PITCH, ERR_064_LIGATURE_DURATION, - ERR_065_MREST_INVALID_MEASURE + ERR_065_MREST_INVALID_MEASURE, + ERR_066_EMPTY_CONTAINER }; // clang-format off @@ -2365,7 +2366,8 @@ const std::map PAEInput::s_errCodes{ { ERR_062_LIGATURE_NOTE_AFTER, "To indicate a ligature, a '+' must be followed by a note." }, { ERR_063_LIGATURE_PITCH, "A ligature cannot have two consecutive notes with the same pitch." }, { ERR_064_LIGATURE_DURATION, "The duration in a ligature cannot be shorter than a semibreve." }, - { ERR_065_MREST_INVALID_MEASURE, "A measure with a measure rest cannot include anything else." } + { ERR_065_MREST_INVALID_MEASURE, "A measure with a measure rest cannot include anything else." }, + { ERR_066_EMPTY_CONTAINER, "A grace group or a beam cannot be empty." } }; // clang-format on @@ -2419,6 +2421,7 @@ namespace pae { m_inputChar = c; m_position = position; m_object = object; + m_treeObject = NULL; m_isError = false; } @@ -2457,6 +2460,12 @@ namespace pae { return name; } + void Token::SetInTree() + { + m_treeObject = m_object; + m_object = NULL; + } + } // namespace pae PAEInput::PAEInput(Doc *doc) : Input(doc) @@ -2966,8 +2975,6 @@ bool PAEInput::Parse() if (success) success = this->CheckHierarchy(); - LogDebugTokens(); - if (m_pedanticMode && !success) { this->ClearTokenObjects(); return false; @@ -3046,7 +3053,7 @@ bool PAEInput::Parse() if (token.Is(MEASURE)) { currentMeasure = vrv_cast(token.m_object); assert(currentMeasure); - token.m_object = NULL; + token.SetInTree(); section->AddChild(currentMeasure); Staff *staff = new Staff(1); @@ -3092,7 +3099,7 @@ bool PAEInput::Parse() } } // Object are own by the scoreDef - token.m_object = NULL; + token.SetInTree(); continue; } else if (token.m_object->IsLayerElement()) { @@ -3100,7 +3107,7 @@ bool PAEInput::Parse() LayerElement *element = vrv_cast(token.m_object); assert(element); // The object is either a container end, or will be added to the layerElementContainers.back() - token.m_object = NULL; + token.SetInTree(); // For a container end, no object to add to the doc. if (token.m_char == pae::CONTAINER_END) { @@ -3144,12 +3151,14 @@ bool PAEInput::Parse() tie->SetTstamp2({ 0, tstamp2 }); } } - token.m_object = NULL; + token.SetInTree(); } } CheckContentPostBuild(); + LogDebugTokens(); + // We should have no object left, just in case they need to be delete. this->ClearTokenObjects(); @@ -4728,6 +4737,22 @@ bool PAEInput::CheckContentPostBuild() // * graceGrp should not be empty // * keySig / meterSig change more than once in a measure + ClassIdsComparison comparison({ BEAM, GRACEGRP }); + ClassIdsComparison noteOrRest({ NOTE, REST }); + ListOfObjects containers; + m_doc->FindAllDescendantsByComparison(&containers, &comparison); + for (auto &container : containers) { + ListOfObjects notesOrRests; + container->FindAllDescendantsByComparison(¬esOrRests, ¬eOrRest); + if ((int)notesOrRests.size() < 1) { + pae::Token *token = this->GetTokenForTreeObject(container); + if (token) { + LogPAE(ERR_066_EMPTY_CONTAINER, *token); + if (m_pedanticMode) return false; + } + } + } + return true; } @@ -4750,6 +4775,14 @@ void PAEInput::RemoveContainerToken(Object *object) } } +pae::Token *PAEInput::GetTokenForTreeObject(Object *object) +{ + for (pae::Token &token : m_pae) { + if (token.m_treeObject == object) return &token; + } + return NULL; +} + bool PAEInput::ParseKeySig(KeySig *keySig, const std::string &paeStr, pae::Token &token) { assert(keySig);