@@ -429,153 +429,145 @@ void VerifyXML(const std::string& xml_text,
429
429
const std::string ID = node->Attribute (" ID" ) ? node->Attribute (" ID" ) : " " ;
430
430
const int line_number = node->GetLineNum ();
431
431
432
- if (name == " Decorator" )
432
+ // Precondition: built-in XML element types must define attribute [ID]
433
+ const bool is_builtin =
434
+ (name == " Decorator" || name == " Action" || name == " Condition" ||
435
+ name == " Control" || name == " SubTree" );
436
+ if (is_builtin && ID.empty ())
433
437
{
434
- if (ID.empty ())
435
- {
436
- ThrowError (line_number, " The tag <Decorator> must have the "
437
- " attribute [ID]" );
438
- }
439
- if (children_count != 1 )
440
- {
441
- ThrowError (line_number, " The tag <Decorator> with ID '" + ID +
442
- " ' must have exactly 1 "
443
- " child" );
444
- }
445
- }
446
- else if (name == " Action" )
447
- {
448
- if (ID.empty ())
449
- {
450
- ThrowError (line_number, " The tag <Action> must have the "
451
- " attribute [ID]" );
452
- }
453
- if (children_count != 0 )
454
- {
455
- ThrowError (line_number, " The tag <Action> with ID '" + ID +
456
- " ' must not have any "
457
- " child" );
458
- }
438
+ ThrowError (line_number, std::string (" The tag <" ) + name +
439
+ " > must have the attribute [ID]" );
459
440
}
460
- else if (name == " Condition" )
441
+
442
+ if (name == " BehaviorTree" )
461
443
{
462
- if (ID.empty ())
463
- {
464
- ThrowError (line_number, " The tag <Condition> must have the "
465
- " attribute [ID]" );
466
- }
467
- if (children_count != 0 )
444
+ if (ID.empty () && behavior_tree_count > 1 )
468
445
{
469
- ThrowError (line_number, " The tag <Condition> with ID '" + ID +
470
- " ' must not have any "
471
- " child" );
446
+ ThrowError (line_number, " The tag <BehaviorTree> must have the attribute [ID]" );
472
447
}
473
- }
474
- else if (name == " Control" )
475
- {
476
- if (ID.empty ())
448
+ if (registered_nodes.count (ID) != 0 )
477
449
{
478
- ThrowError (line_number, " The tag <Control> must have the "
479
- " attribute [ID]" );
450
+ ThrowError (line_number, " The attribute [ID] of tag <BehaviorTree> must not use the name of a registered Node" );
480
451
}
481
- if (children_count == 0 )
452
+ if (children_count != 1 )
482
453
{
483
- ThrowError (line_number, " The tag <Control> with ID '" + ID +
484
- " ' must have at least 1 "
485
- " child" );
454
+ ThrowError (line_number, " The tag <BehaviorTree> with ID '" + ID + " ' must have exactly 1 child" );
486
455
}
487
456
}
488
457
else if (name == " SubTree" )
489
458
{
490
- if (ID.empty ())
491
- {
492
- ThrowError (line_number, " The tag <SubTree> must have the "
493
- " attribute [ID]" );
494
- }
495
459
if (children_count != 0 )
496
460
{
497
- ThrowError (line_number,
498
- " <SubTree> with ID '" + ID + " ' should not have any child" );
461
+ ThrowError (line_number, " <SubTree> with ID '" + ID + " ' should not have any child" );
499
462
}
500
463
if (registered_nodes.count (ID) != 0 )
501
464
{
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 (registered_nodes.count (ID) != 0 )
514
- {
515
- ThrowError (line_number, " The attribute [ID] of tag <BehaviorTree> "
516
- " must not use the name of a registered Node" );
517
- }
518
- if (children_count != 1 )
519
- {
520
- ThrowError (line_number, " The tag <BehaviorTree> with ID '" + ID +
521
- " ' must have exactly 1 "
522
- " child" );
465
+ ThrowError (line_number, " The attribute [ID] of tag <SubTree> must not use the name of a registered Node" );
523
466
}
467
+ // no further validation for SubTree
524
468
}
525
469
else
526
470
{
527
- // search in the factory and the list of subtrees
528
- const auto search = registered_nodes. find ( name);
529
- bool found = ( search ! = registered_nodes.end () );
530
- if (!found )
471
+ // Unified lookup: use ID for builtin wrapper tags, otherwise use the element name
472
+ const std::string lookup_key = (is_builtin ? ID : name);
473
+ const auto search = registered_nodes.find (lookup_key );
474
+ if (search == registered_nodes. end () )
531
475
{
532
- ThrowError (line_number, std::string (" Node not recognized: " ) + name);
533
- }
534
-
535
- if (search->second == NodeType::DECORATOR)
536
- {
537
- if (children_count != 1 )
476
+ if (is_builtin)
477
+ {
478
+ ThrowError (line_number, std::string (" ID '" ) + ID + " ' is not a registered node" );
479
+ }
480
+ else
538
481
{
539
- ThrowError (line_number, std::string (" The node <" ) + name + " > with ID '" + ID +
540
- " ' must have exactly 1 child" );
482
+ ThrowError (line_number, std::string (" Node not recognized: " ) + name);
541
483
}
542
484
}
543
- else if (search-> second == NodeType::CONTROL)
485
+ else
544
486
{
545
- if (children_count == 0 )
487
+ const auto node_type = search->second ;
488
+ if (node_type == NodeType::DECORATOR)
546
489
{
547
- ThrowError (line_number, std::string (" The node <" ) + name + " > with ID '" + ID +
548
- " ' must have 1 or more children" );
490
+ if (children_count != 1 )
491
+ {
492
+ if (is_builtin)
493
+ {
494
+ ThrowError (line_number, std::string (" The Decorator node '" ) + ID + " ' must have exactly 1 child" );
495
+ }
496
+ else
497
+ {
498
+ ThrowError (line_number, std::string (" The node <" ) + name + " > with ID '" + ID + " ' must have exactly 1 child" );
499
+ }
500
+ }
549
501
}
550
- if (name == " ReactiveSequence " )
502
+ else if (node_type == NodeType::CONTROL )
551
503
{
552
- size_t async_count = 0 ;
553
- for (auto child = node->FirstChildElement (); child != nullptr ;
554
- child = child->NextSiblingElement ())
504
+ if (children_count == 0 )
555
505
{
556
- const std::string child_name = child->Name ();
557
- const auto child_search = registered_nodes.find (child_name);
558
- if (child_search == registered_nodes.end ())
506
+ if (is_builtin)
507
+ {
508
+ ThrowError (line_number, std::string (" The Control node '" ) + ID + " ' must have 1 or more children" );
509
+ }
510
+ else
559
511
{
560
- ThrowError (child->GetLineNum (),
561
- std::string (" Unknown node type: " ) + child_name);
512
+ ThrowError (line_number, std::string (" The node <" ) + name + " > with ID '" + ID + " ' must have 1 or more children" );
562
513
}
563
- const auto child_type = child_search->second ;
564
- if (child_type == NodeType::CONTROL &&
565
- ((child_name == " ThreadedAction" ) ||
566
- (child_name == " StatefulActionNode" ) ||
567
- (child_name == " CoroActionNode" ) || (child_name == " AsyncSequence" )))
514
+ }
515
+ if (lookup_key == " ReactiveSequence" )
516
+ {
517
+ size_t async_count = 0 ;
518
+ for (auto child = node->FirstChildElement (); child != nullptr ;
519
+ child = child->NextSiblingElement ())
568
520
{
569
- ++async_count;
570
- if (async_count > 1 )
521
+ const std::string child_name = child->Name ();
522
+ const auto child_search = registered_nodes.find (child_name);
523
+ if (child_search == registered_nodes.end ())
524
+ {
525
+ ThrowError (child->GetLineNum (), std::string (" Unknown node type: " ) + child_name);
526
+ }
527
+ const auto child_type = child_search->second ;
528
+ if (child_type == NodeType::CONTROL &&
529
+ ((child_name == " ThreadedAction" ) ||
530
+ (child_name == " StatefulActionNode" ) ||
531
+ (child_name == " CoroActionNode" ) || (child_name == " AsyncSequence" )))
571
532
{
572
- ThrowError (line_number, std::string (" A ReactiveSequence with ID '" + ID +
573
- " ' cannot have more "
574
- " than one async child." ));
533
+ ++async_count;
534
+ if (async_count > 1 )
535
+ {
536
+ ThrowError (line_number, std::string (" A ReactiveSequence with ID '" ) + ID +
537
+ " ' cannot have more than one async child." );
538
+ }
575
539
}
576
540
}
577
541
}
578
542
}
543
+ else if (node_type == NodeType::ACTION)
544
+ {
545
+ if (children_count != 0 )
546
+ {
547
+ if (is_builtin)
548
+ {
549
+ ThrowError (line_number, std::string (" The Action node '" ) + ID + " ' must not have any child" );
550
+ }
551
+ else
552
+ {
553
+ ThrowError (line_number, std::string (" The node <" ) + name + " > with ID '" + ID + " ' must not have any child" );
554
+ }
555
+ }
556
+ }
557
+ else if (node_type == NodeType::CONDITION)
558
+ {
559
+ if (children_count != 0 )
560
+ {
561
+ if (is_builtin)
562
+ {
563
+ ThrowError (line_number, std::string (" The Condition node '" ) + ID + " ' must not have any child" );
564
+ }
565
+ else
566
+ {
567
+ ThrowError (line_number, std::string (" The node <" ) + name + " > with ID '" + ID + " ' must not have any child" );
568
+ }
569
+ }
570
+ }
579
571
}
580
572
}
581
573
// recursion
0 commit comments