Skip to content

Commit cf180c0

Browse files
robin-muellerCopilot
andcommitted
add remove_whitespace when serializing trees
Co-authored-by: Copilot <copilot@github.com>
1 parent 5405700 commit cf180c0

3 files changed

Lines changed: 108 additions & 14 deletions

File tree

auto_apms_behavior_tree_core/include/auto_apms_behavior_tree_core/tree/tree_document.hpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -983,9 +983,12 @@ class TreeDocument : private tinyxml2::XMLDocument
983983
*
984984
* In contrast to TreeDocument::writeToString, the resulting XML contains the tree element as its root element,
985985
* emphasizing the fact that this function returns an XML for a single behavior tree only.
986+
* @param remove_whitespace If `true`, insignificant whitespace (indentation and newlines between elements) is
987+
* omitted from the output, producing a compact single-line representation. If `false` (default), the output is
988+
* pretty-printed with standard indentation.
986989
* @return String representing the behavior tree in XML format.
987990
*/
988-
std::string writeToString() const;
991+
std::string writeToString(bool remove_whitespace = false) const;
989992

990993
/**
991994
* This function is an exact reimplementation of NodeElement::removeFirstChild(const std::string &, const
@@ -1482,15 +1485,22 @@ class TreeDocument : private tinyxml2::XMLDocument
14821485

14831486
/**
14841487
* @brief Write the XML of this tree document to a string.
1488+
*
1489+
* @param remove_whitespace If `true`, insignificant whitespace (indentation and newlines between elements) is
1490+
* omitted from the output, producing a compact single-line representation.
14851491
* @return String representing this document in XML format.
14861492
*/
1487-
std::string writeToString() const;
1493+
std::string writeToString(bool remove_whitespace = false) const;
14881494

14891495
/**
14901496
* @brief Write the XML of this tree document to a file.
1497+
*
14911498
* @param path Path to the output file.
1499+
* @param remove_whitespace If `true`, insignificant whitespace (indentation and newlines between elements) is
1500+
* omitted from the output, producing a compact single-line file. If `false` (default), the file is
1501+
* pretty-printed with standard indentation.
14921502
*/
1493-
void writeToFile(const std::string & path) const;
1503+
void writeToFile(const std::string & path, bool remove_whitespace = false) const;
14941504

14951505
/**
14961506
* @brief Clear this document and reset it to its initial state.

auto_apms_behavior_tree_core/src/tree/tree_document.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
1616

1717
#include <algorithm>
18+
#include <cstdio>
1819
#include <filesystem>
1920

2021
#include "ament_index_cpp/get_package_share_directory.hpp"
@@ -645,11 +646,11 @@ BT::Result TreeDocument::TreeElement::verify() const
645646
return doc.mergeTree(*this, true).verify();
646647
}
647648

648-
std::string TreeDocument::TreeElement::writeToString() const
649+
std::string TreeDocument::TreeElement::writeToString(bool remove_whitespace) const
649650
{
650651
XMLDocument tree_doc;
651652
tree_doc.InsertEndChild(ele_ptr_->DeepClone(&tree_doc));
652-
tinyxml2::XMLPrinter printer;
653+
tinyxml2::XMLPrinter printer(nullptr, remove_whitespace);
653654
tree_doc.Print(&printer);
654655
return printer.CStr();
655656
}
@@ -1422,21 +1423,24 @@ BT::Result TreeDocument::verify() const
14221423
return {};
14231424
}
14241425

1425-
std::string TreeDocument::writeToString() const
1426+
std::string TreeDocument::writeToString(bool remove_whitespace) const
14261427
{
1427-
tinyxml2::XMLPrinter printer;
1428+
tinyxml2::XMLPrinter printer(nullptr, remove_whitespace);
14281429
Print(&printer);
14291430
return printer.CStr();
14301431
}
14311432

1432-
void TreeDocument::writeToFile(const std::string & path) const
1433+
void TreeDocument::writeToFile(const std::string & path, bool remove_whitespace) const
14331434
{
1434-
XMLDocument doc;
1435-
DeepCopy(&doc);
1436-
tinyxml2::XMLError result = doc.SaveFile(path.c_str());
1437-
if (result != tinyxml2::XML_SUCCESS) {
1438-
throw exceptions::TreeDocumentError(
1439-
"Failed to write tree document to file. Error ID: " + std::string(doc.ErrorIDToName(result)));
1435+
const std::string xml = writeToString(remove_whitespace);
1436+
FILE * f = std::fopen(path.c_str(), "w");
1437+
if (!f) {
1438+
throw exceptions::TreeDocumentError("Failed to open file for writing: " + path);
1439+
}
1440+
const std::size_t written = std::fwrite(xml.data(), 1, xml.size(), f);
1441+
const int close_err = std::fclose(f);
1442+
if (written != xml.size() || close_err != 0) {
1443+
throw exceptions::TreeDocumentError("Failed to write tree document to file: " + path);
14401444
}
14411445
}
14421446

auto_apms_behavior_tree_core/test/unit/tree_document_tree_generation.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
#include <gtest/gtest.h>
1616

17+
#include <cstdlib>
18+
#include <fstream>
19+
1720
#include "auto_apms_behavior_tree_core/exceptions.hpp"
1821
#include "auto_apms_behavior_tree_core/node/node_model_type.hpp"
1922
#include "testable_tree_document.hpp"
@@ -572,6 +575,83 @@ TEST_F(TreeDocumentTreeGenerationTest, WriteToStringWithTrees)
572575
EXPECT_TRUE(xml.find("BehaviorTree") != std::string::npos);
573576
}
574577

578+
TEST_F(TreeDocumentTreeGenerationTest, WriteToStringRemoveWhitespaceContainsNoNewlines)
579+
{
580+
doc_->newTree("TestTree");
581+
582+
std::string xml = doc_->writeToString(true);
583+
584+
EXPECT_TRUE(xml.find('\n') == std::string::npos);
585+
EXPECT_TRUE(xml.find("TestTree") != std::string::npos);
586+
}
587+
588+
TEST_F(TreeDocumentTreeGenerationTest, WriteToStringRemoveWhitespaceShorterThanPrettyPrinted)
589+
{
590+
doc_->newTree("TestTree");
591+
592+
EXPECT_LT(doc_->writeToString(true).size(), doc_->writeToString(false).size());
593+
}
594+
595+
TEST_F(TreeDocumentTreeGenerationTest, WriteToStringRemoveWhitespaceRoundTrips)
596+
{
597+
// A document serialized without whitespace must still be parseable.
598+
doc_->newTree("TestTree").makeRoot().insertNode("Sequence");
599+
600+
const std::string compact = doc_->writeToString(true);
601+
602+
TreeDocument round_trip;
603+
EXPECT_NO_THROW(round_trip.mergeString(compact));
604+
EXPECT_TRUE(round_trip.hasTreeName("TestTree"));
605+
}
606+
607+
TEST_F(TreeDocumentTreeGenerationTest, TreeElementWriteToStringRemoveWhitespaceContainsNoNewlines)
608+
{
609+
auto tree = doc_->newTree("TestTree");
610+
611+
std::string xml = tree.writeToString(true);
612+
613+
EXPECT_TRUE(xml.find('\n') == std::string::npos);
614+
EXPECT_TRUE(xml.find("TestTree") != std::string::npos);
615+
}
616+
617+
TEST_F(TreeDocumentTreeGenerationTest, TreeElementWriteToStringRemoveWhitespaceShorterThanPrettyPrinted)
618+
{
619+
auto tree = doc_->newTree("TestTree");
620+
621+
EXPECT_LT(tree.writeToString(true).size(), tree.writeToString(false).size());
622+
}
623+
624+
TEST_F(TreeDocumentTreeGenerationTest, WriteToFileRemoveWhitespaceParseable)
625+
{
626+
doc_->newTree("TestTree").makeRoot().insertNode("Sequence");
627+
628+
const std::string path =
629+
std::string(std::getenv("TEST_TMPDIR") ? std::getenv("TEST_TMPDIR") : "/tmp") + "/tree_doc_compact.xml";
630+
ASSERT_NO_THROW(doc_->writeToFile(path, true));
631+
632+
TreeDocument loaded;
633+
ASSERT_NO_THROW(loaded.mergeFile(path));
634+
EXPECT_TRUE(loaded.hasTreeName("TestTree"));
635+
636+
// File must contain no newlines (compact format).
637+
std::ifstream f(path);
638+
std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
639+
EXPECT_TRUE(content.find('\n') == std::string::npos);
640+
}
641+
642+
TEST_F(TreeDocumentTreeGenerationTest, WriteToFilePrettyPrintedContainsNewlines)
643+
{
644+
doc_->newTree("TestTree").makeRoot().insertNode("Sequence");
645+
646+
const std::string path =
647+
std::string(std::getenv("TEST_TMPDIR") ? std::getenv("TEST_TMPDIR") : "/tmp") + "/tree_doc_pretty.xml";
648+
ASSERT_NO_THROW(doc_->writeToFile(path, false));
649+
650+
std::ifstream f(path);
651+
std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
652+
EXPECT_TRUE(content.find('\n') != std::string::npos);
653+
}
654+
575655
// =============================================================================
576656
// verify Tests
577657
// =============================================================================

0 commit comments

Comments
 (0)