Skip to content

Commit 2ed6d49

Browse files
authored
Clean up VerifyXML logic (#1000)
* Refactor VerifyXML to clarify logic - Reduces duplication in VerifyXML by handling the ID check for built-in node types up front so they can then be definitively looked up in the registered nodes. - Enhances error messaging in VerifyXML by using *either* the node name *or* the ID, depending on which is appropriate, instead of leaving users guessing "which Decorator is wrong" - Fixes custom Action and Condition nodes using shorthand syntax not being properly verified - Fixes `<Control ID="ReactiveSequence"/>` not being verified with the same logic as `<ReactiveSequence/>` - Fixes `<Action ID="MyAction"/>` not triggering a behavior lookup when `<MyAction/>` would. * fix tests that were failing due to bad assumptions
1 parent 8750370 commit 2ed6d49

File tree

2 files changed

+55
-83
lines changed

2 files changed

+55
-83
lines changed

src/xml_parsing.cpp

Lines changed: 53 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -434,119 +434,76 @@ void VerifyXML(const std::string& xml_text,
434434
const std::string ID = node->Attribute("ID") ? node->Attribute("ID") : "";
435435
const int line_number = node->GetLineNum();
436436

437-
if(name == "Decorator")
437+
// Precondition: built-in XML element types must define attribute [ID]
438+
const bool is_builtin =
439+
(name == "Decorator" || name == "Action" || name == "Condition" ||
440+
name == "Control" || name == "SubTree");
441+
if(is_builtin && ID.empty())
438442
{
439-
if(children_count != 1)
440-
{
441-
ThrowError(line_number, "The tag <Decorator> must have exactly 1 "
442-
"child");
443-
}
444-
if(ID.empty())
445-
{
446-
ThrowError(line_number, "The tag <Decorator> must have the "
447-
"attribute [ID]");
448-
}
449-
}
450-
else if(name == "Action")
451-
{
452-
if(children_count != 0)
453-
{
454-
ThrowError(line_number, "The tag <Action> must not have any "
455-
"child");
456-
}
457-
if(ID.empty())
458-
{
459-
ThrowError(line_number, "The tag <Action> must have the "
460-
"attribute [ID]");
461-
}
443+
ThrowError(line_number,
444+
std::string("The tag <") + name + "> must have the attribute [ID]");
462445
}
463-
else if(name == "Condition")
446+
447+
if(name == "BehaviorTree")
464448
{
465-
if(children_count != 0)
466-
{
467-
ThrowError(line_number, "The tag <Condition> must not have any "
468-
"child");
469-
}
470-
if(ID.empty())
449+
if(ID.empty() && behavior_tree_count > 1)
471450
{
472-
ThrowError(line_number, "The tag <Condition> must have the "
473-
"attribute [ID]");
451+
ThrowError(line_number, "The tag <BehaviorTree> must have the attribute [ID]");
474452
}
475-
}
476-
else if(name == "Control")
477-
{
478-
if(children_count == 0)
453+
if(registered_nodes.count(ID) != 0)
479454
{
480-
ThrowError(line_number, "The tag <Control> must have at least 1 "
481-
"child");
455+
ThrowError(line_number, "The attribute [ID] of tag <BehaviorTree> must not use "
456+
"the name of a registered Node");
482457
}
483-
if(ID.empty())
458+
if(children_count != 1)
484459
{
485-
ThrowError(line_number, "The tag <Control> must have the "
486-
"attribute [ID]");
460+
ThrowError(line_number, "The tag <BehaviorTree> with ID '" + ID +
461+
"' must have exactly 1 child");
487462
}
488463
}
489464
else if(name == "SubTree")
490465
{
491466
if(children_count != 0)
492467
{
493-
ThrowError(line_number, "<SubTree> should not have any child");
494-
}
495-
if(ID.empty())
496-
{
497-
ThrowError(line_number, "The tag <SubTree> must have the "
498-
"attribute [ID]");
468+
ThrowError(line_number,
469+
"<SubTree> with ID '" + ID + "' should not have any child");
499470
}
500471
if(registered_nodes.count(ID) != 0)
501472
{
502-
ThrowError(line_number, "The attribute [ID] of tag <SubTree> must "
503-
"not use the name of a registered Node");
504-
}
505-
}
506-
else if(name == "BehaviorTree")
507-
{
508-
if(ID.empty() && behavior_tree_count > 1)
509-
{
510-
ThrowError(line_number, "The tag <BehaviorTree> must have the "
511-
"attribute [ID]");
512-
}
513-
if(children_count != 1)
514-
{
515-
ThrowError(line_number, "The tag <BehaviorTree> must have exactly 1 "
516-
"child");
517-
}
518-
if(registered_nodes.count(ID) != 0)
519-
{
520-
ThrowError(line_number, "The attribute [ID] of tag <BehaviorTree> "
521-
"must not use the name of a registered Node");
473+
ThrowError(line_number, "The attribute [ID] of tag <SubTree> must not use the "
474+
"name of a registered Node");
522475
}
523476
}
524477
else
525478
{
526-
// search in the factory and the list of subtrees
527-
const auto search = registered_nodes.find(name);
479+
// use ID for builtin node types, otherwise use the element name
480+
const auto lookup_name = is_builtin ? ID : name;
481+
const auto search = registered_nodes.find(lookup_name);
528482
bool found = (search != registered_nodes.end());
529483
if(!found)
530484
{
531-
ThrowError(line_number, std::string("Node not recognized: ") + name);
485+
ThrowError(line_number, std::string("Node not recognized: ") + lookup_name);
532486
}
533487

534-
if(search->second == NodeType::DECORATOR)
488+
const auto node_type = search->second;
489+
const std::string& registered_name = search->first;
490+
491+
if(node_type == NodeType::DECORATOR)
535492
{
536493
if(children_count != 1)
537494
{
538-
ThrowError(line_number,
539-
std::string("The node <") + name + "> must have exactly 1 child");
495+
ThrowError(line_number, std::string("The node '") + registered_name +
496+
"' must have exactly 1 child");
540497
}
541498
}
542-
else if(search->second == NodeType::CONTROL)
499+
else if(node_type == NodeType::CONTROL)
543500
{
544501
if(children_count == 0)
545502
{
546-
ThrowError(line_number,
547-
std::string("The node <") + name + "> must have 1 or more children");
503+
ThrowError(line_number, std::string("The node '") + registered_name +
504+
"' must have 1 or more children");
548505
}
549-
if(name == "ReactiveSequence")
506+
if(registered_name == "ReactiveSequence")
550507
{
551508
size_t async_count = 0;
552509
for(auto child = node->FirstChildElement(); child != nullptr;
@@ -568,13 +525,29 @@ void VerifyXML(const std::string& xml_text,
568525
++async_count;
569526
if(async_count > 1)
570527
{
571-
ThrowError(line_number, std::string("A ReactiveSequence cannot have more "
572-
"than one async child."));
528+
ThrowError(line_number, std::string("A ReactiveSequence cannot have "
529+
"more than one async child."));
573530
}
574531
}
575532
}
576533
}
577534
}
535+
else if(node_type == NodeType::ACTION)
536+
{
537+
if(children_count != 0)
538+
{
539+
ThrowError(line_number, std::string("The node '") + registered_name +
540+
"' must not have any child");
541+
}
542+
}
543+
else if(node_type == NodeType::CONDITION)
544+
{
545+
if(children_count != 0)
546+
{
547+
ThrowError(line_number, std::string("The node '") + registered_name +
548+
"' must not have any child");
549+
}
550+
}
578551
}
579552
//recursion
580553
for(auto child = node->FirstChildElement(); child != nullptr;

tests/gtest_factory.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ static const char* xml_text_subtree_part1 = R"(
8282
<BehaviorTree ID="MainTree">
8383
<Fallback name="root_selector">
8484
<SubTree ID="DoorClosedSubtree" />
85-
<Action ID="PassThroughWindow" />
85+
<Action ID="PassThroughDoor" />
8686
</Fallback>
8787
</BehaviorTree>
8888
</root> )";
@@ -93,11 +93,10 @@ static const char* xml_text_subtree_part2 = R"(
9393
<BehaviorTree ID="DoorClosedSubtree">
9494
<Sequence name="door_sequence">
9595
<Decorator ID="Inverter">
96-
<Action ID="IsDoorLocked" />
96+
<Action ID="IsDoorClosed" />
9797
</Decorator>
9898
<Action ID="OpenDoor" />
9999
<Action ID="PassThroughDoor" />
100-
<Action ID="CloseDoor" />
101100
</Sequence>
102101
</BehaviorTree>
103102
</root> )";

0 commit comments

Comments
 (0)