From 6cebcefb15dab1d4f80bedff4e73ab98a9415885 Mon Sep 17 00:00:00 2001 From: dyrpsf Date: Sat, 27 Jun 2026 15:49:53 +0530 Subject: [PATCH 1/3] Fix #354: Remove empty RDF Description nodes after unsetModifiedDates --- src/sbml/SBase.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/sbml/SBase.cpp b/src/sbml/SBase.cpp index 081e5b9988..e1a643368c 100644 --- a/src/sbml/SBase.cpp +++ b/src/sbml/SBase.cpp @@ -6470,6 +6470,32 @@ SBase::reconstructRDFAnnotation() } } +if (mAnnotation != NULL) + { + int rdfIndex = mAnnotation->getIndex("RDF"); + if (rdfIndex >= 0) + { + XMLNode& rdfNode = mAnnotation->getChild((unsigned int)rdfIndex); + + // Iterate backwards since we are removing items from the list + for (int i = (int)rdfNode.getNumChildren() - 1; i >= 0; i--) + { + XMLNode& child = rdfNode.getChild((unsigned int)i); + + // If the Description node has no inner children, delete it + if (child.getName() == "Description" && child.getNumChildren() == 0) + { + delete rdfNode.removeChild((unsigned int)i); + } + } + + // If the RDF node itself is now completely empty, delete it as well + if (rdfNode.getNumChildren() == 0) + { + delete mAnnotation->removeChild((unsigned int)rdfIndex); + } + } + } if (history != NULL) delete history; if (cvTerms != NULL) delete cvTerms; From bc0e4c41c7b445655d18f9933d0ec876082fb926 Mon Sep 17 00:00:00 2001 From: dyrpsf Date: Sat, 27 Jun 2026 16:05:55 +0530 Subject: [PATCH 2/3] Add unit test for unsetModifiedDates (Issue #354) --- src/sbml/test/TestSBase.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/sbml/test/TestSBase.cpp b/src/sbml/test/TestSBase.cpp index 2efd046fc1..0c015d5106 100644 --- a/src/sbml/test/TestSBase.cpp +++ b/src/sbml/test/TestSBase.cpp @@ -2654,6 +2654,35 @@ START_TEST(test_SBase_userData1) } END_TEST +START_TEST (test_SBase_unsetModifiedDates_issue354) +{ + Model* m = new Model(3, 2); + m->setMetaId("foo"); + + ModelHistory* h = new ModelHistory(); + Date* d = new Date("2019-07-29T10:53:09Z"); + h->addModifiedDate(d); + + m->setModelHistory(h); + + // Ensure the date was successfully added + fail_unless(m->getNumModifiedDates() == 1); + + // Unset the dates (This triggers the new cleanup logic we wrote in SBase.cpp) + m->unsetModifiedDates(); + + // Force the annotation to sync and generate the XML string + std::string annotation = m->getAnnotationString(); + + // Assert that the empty Description detritus was completely removed + size_t found = annotation.find(""); + fail_unless(found == std::string::npos); + + delete d; + delete m; +} +END_TEST + Suite * create_suite_SBase (void) { @@ -2709,6 +2738,7 @@ create_suite_SBase (void) tcase_add_test(tcase, test_SBase_userData1); tcase_add_test(tcase, test_SBase_prefixMetaIdSBO); + tcase_add_test(tcase, test_SBase_unsetModifiedDates_issue354); suite_add_tcase(suite, tcase); From 83599287b8aa13c081831e75b0ce6f9b5213f2e1 Mon Sep 17 00:00:00 2001 From: dyrpsf Date: Tue, 30 Jun 2026 10:11:13 +0530 Subject: [PATCH 3/3] Apply fix for issue #354 --- src/sbml/SBase.cpp | 4 ---- src/sbml/test/TestSBase.cpp | 13 ++++++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/sbml/SBase.cpp b/src/sbml/SBase.cpp index e1a643368c..4b8b1dafba 100644 --- a/src/sbml/SBase.cpp +++ b/src/sbml/SBase.cpp @@ -6477,19 +6477,15 @@ if (mAnnotation != NULL) { XMLNode& rdfNode = mAnnotation->getChild((unsigned int)rdfIndex); - // Iterate backwards since we are removing items from the list for (int i = (int)rdfNode.getNumChildren() - 1; i >= 0; i--) { XMLNode& child = rdfNode.getChild((unsigned int)i); - - // If the Description node has no inner children, delete it if (child.getName() == "Description" && child.getNumChildren() == 0) { delete rdfNode.removeChild((unsigned int)i); } } - // If the RDF node itself is now completely empty, delete it as well if (rdfNode.getNumChildren() == 0) { delete mAnnotation->removeChild((unsigned int)rdfIndex); diff --git a/src/sbml/test/TestSBase.cpp b/src/sbml/test/TestSBase.cpp index 0c015d5106..89786909e5 100644 --- a/src/sbml/test/TestSBase.cpp +++ b/src/sbml/test/TestSBase.cpp @@ -2665,20 +2665,19 @@ START_TEST (test_SBase_unsetModifiedDates_issue354) m->setModelHistory(h); - // Ensure the date was successfully added - fail_unless(m->getNumModifiedDates() == 1); + fail_unless(m->getModelHistory()->getNumModifiedDates() == 1); - // Unset the dates (This triggers the new cleanup logic we wrote in SBase.cpp) - m->unsetModifiedDates(); + ModelHistory* h_ref = m->getModelHistory(); + h_ref->unsetModifiedDates(); + m->setModelHistory(h_ref); - // Force the annotation to sync and generate the XML string std::string annotation = m->getAnnotationString(); - // Assert that the empty Description detritus was completely removed - size_t found = annotation.find(""); + size_t found = annotation.find("rdf:about=\"#foo\""); fail_unless(found == std::string::npos); delete d; + delete h; delete m; } END_TEST