FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
serial::xml_parser_utils Namespace Reference

Classes

struct  AssetLoaders
 Container for functions that load external file-backed assets. More...
 
struct  ParserContext
 Encapsulates the state required during the XML parsing process. More...
 
struct  ReferenceLookup
 Holds maps to resolve string names to internal SimId references during XML parsing. More...
 

Functions

RealType get_child_real_type (const XmlElement &element, const std::string &elementName)
 Extracts a floating-point (RealType) value from a named child element.
 
bool get_attribute_bool (const XmlElement &element, const std::string &attributeName, bool defaultVal)
 Extracts a boolean value from a named attribute.
 
SimId assign_id_from_attribute (const std::string &owner, ObjectType type)
 Generates a unique SimId based on the requested object type.
 
SimId resolve_reference_id (const XmlElement &element, const std::string &attributeName, const std::string &owner, const std::unordered_map< std::string, SimId > &name_map)
 Resolves an XML string reference into an internal SimId.
 
std::vector< radar::SchedulePeriodparseSchedule (const XmlElement &parent, const std::string &parentName, bool isPulsed, RealType pri=0.0)
 Parses a schedule (active periods) for a transmitter or receiver.
 
unsigned parseUnsignedParameter (const std::string_view param_name, const RealType raw_value)
 
template<typename Setter >
void setOptionalRealParameter (const XmlElement &parameters, const std::string &param_name, const RealType default_value, Setter setter)
 
template<typename Setter >
void setOptionalUnsignedParameter (const XmlElement &parameters, const std::string &param_name, const unsigned default_value, Setter setter)
 
void parseOptionalNumericParameters (const XmlElement &parameters, params::Parameters &params_out)
 
void parseRotationAngleUnit (const XmlElement &parameters, params::Parameters &params_out)
 
bool parseOriginParameter (const XmlElement &parameters, params::Parameters &params_out)
 
void parseUtmCoordinateSystem (const XmlElement &cs_element, params::Parameters &params_out)
 
void parseCoordinateSystemParameter (const XmlElement &parameters, params::Parameters &params_out, const bool origin_set)
 
void parseParameters (const XmlElement &parameters, params::Parameters &params_out)
 Parses the <parameters> block into the isolated context parameters.
 
void parseWaveform (const XmlElement &waveform, ParserContext &ctx)
 Parses a <waveform> block and adds it to the World.
 
void parseTiming (const XmlElement &timing, ParserContext &ctx)
 Parses a <timing> block and adds the prototype timing to the World.
 
void parseAntenna (const XmlElement &antenna, ParserContext &ctx)
 Parses an <antenna> block and adds it to the World.
 
void parseMotionPath (const XmlElement &motionPath, radar::Platform *platform)
 Parses a <motionpath> block and attaches it to a Platform.
 
void parseRotationPath (const XmlElement &rotation, radar::Platform *platform, params::RotationAngleUnit unit)
 Parses a <rotationpath> block and attaches it to a Platform.
 
void parseFixedRotation (const XmlElement &rotation, radar::Platform *platform, params::RotationAngleUnit unit)
 Parses a <fixedrotation> block and attaches it to a Platform.
 
static radar::TransmitterparseTransmitterWithMode (const XmlElement &transmitter, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs, const radar::OperationMode mode)
 Parses a transmitter after its operation mode has already been determined.
 
radar::TransmitterparseTransmitter (const XmlElement &transmitter, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs)
 Parses a <transmitter> block, resolves its dependencies, and adds it to the World.
 
static radar::ReceiverparseReceiverWithMode (const XmlElement &receiver, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs, const radar::OperationMode mode)
 Parses a receiver after its operation mode has already been determined.
 
radar::ReceiverparseReceiver (const XmlElement &receiver, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs)
 Parses a <receiver> block, resolves its dependencies, and adds it to the World.
 
void parseMonostatic (const XmlElement &monostatic, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs)
 Parses a <monostatic> block, creating a linked transmitter and receiver pair.
 
void parseTarget (const XmlElement &target, radar::Platform *platform, ParserContext &ctx)
 Parses a <target> block and adds it to the World.
 
void parsePlatformElements (const XmlElement &platform, ParserContext &ctx, radar::Platform *plat, const std::function< void(const XmlElement &, std::string_view)> &register_name, const ReferenceLookup &refs)
 Iterates and parses all children elements (radars, targets) of a platform.
 
void parsePlatform (const XmlElement &platform, ParserContext &ctx, const std::function< void(const XmlElement &, std::string_view)> &register_name, const ReferenceLookup &refs)
 Parses a complete <platform> block, including its motion paths and sub-elements.
 
void collectIncludeElements (const XmlDocument &doc, const fs::path &currentDir, std::vector< fs::path > &includePaths)
 
bool addIncludeFilesToMainDocument (const XmlDocument &mainDoc, const fs::path &currentDir)
 
void validateXml (bool didCombine, const XmlDocument &mainDoc)
 Validates an XML document against the embedded DTD and XSD schemas.
 
void processParsedDocument (const XmlDocument &doc, ParserContext &ctx)
 Coordinates the full parsing of a validated XML document tree.
 
AssetLoaders createDefaultAssetLoaders ()
 Creates an AssetLoaders struct populated with standard file-I/O implementations.
 
void collectIncludeElements (const XmlDocument &doc, const std::filesystem::path &currentDir, std::vector< std::filesystem::path > &includePaths)
 Recursively finds all <include> tags in a document and resolves their absolute paths.
 
bool addIncludeFilesToMainDocument (const XmlDocument &mainDoc, const std::filesystem::path &currentDir)
 Resolves and merges all <include> files directly into the provided main document.
 

Function Documentation

◆ addIncludeFilesToMainDocument() [1/2]

bool serial::xml_parser_utils::addIncludeFilesToMainDocument ( const XmlDocument mainDoc,
const fs::path &  currentDir 
)

Definition at line 1420 of file xml_parser_utils.cpp.

1421 {
1422 std::vector<fs::path> include_paths;
1424 bool did_combine = false;
1425
1426 for (const auto& include_path : include_paths)
1427 {
1429 if (!included_doc.loadFile(include_path.string()))
1430 {
1431 throw XmlException("Failed to load included XML file: " + include_path.string());
1432 }
1433
1435 did_combine = true;
1436 }
1437
1439 return did_combine;
1440 }
Class for managing XML documents.
Exception class for handling XML-related errors.
void mergeXmlDocuments(const XmlDocument &mainDoc, const XmlDocument &includedDoc)
Merge two XML documents.
void removeIncludeElements(const XmlDocument &doc)
Remove "include" elements from the XML document.
void collectIncludeElements(const XmlDocument &doc, const fs::path &currentDir, std::vector< fs::path > &includePaths)
math::Vec3 max

References collectIncludeElements(), max, mergeXmlDocuments(), and removeIncludeElements().

Referenced by serial::parseSimulation().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ addIncludeFilesToMainDocument() [2/2]

bool serial::xml_parser_utils::addIncludeFilesToMainDocument ( const XmlDocument mainDoc,
const std::filesystem::path &  currentDir 
)

Resolves and merges all <include> files directly into the provided main document.

Parameters
mainDocThe primary XML document that will be mutated.
currentDirThe base directory used to resolve include paths.
Returns
True if at least one file was included and merged, false otherwise.

◆ assign_id_from_attribute()

SimId serial::xml_parser_utils::assign_id_from_attribute ( const std::string &  owner,
ObjectType  type 
)

Generates a unique SimId based on the requested object type.

Parameters
ownerThe name/description of the object requesting the ID (used for logging).
typeThe category/type of the object.
Returns
A newly generated SimId.

Definition at line 360 of file xml_parser_utils.cpp.

361 {
362 const SimId id = SimIdGenerator::instance().generateId(type);
363 LOG(logging::Level::TRACE, "Assigned ID {} to {} (generated)", id, owner);
364 return id;
365 }
SimId generateId(ObjectType type)
Generate a unique SimId for a given object type.
Definition sim_id.h:60
static SimIdGenerator & instance()
Get the singleton instance of SimIdGenerator.
Definition sim_id.h:48
#define LOG(level,...)
Definition logging.h:19
@ TRACE
Trace level for detailed debugging information.
uint64_t SimId
64-bit Unique Simulation ID.
Definition sim_id.h:18

References SimIdGenerator::generateId(), SimIdGenerator::instance(), LOG, max, and logging::TRACE.

Referenced by parseAntenna(), parsePlatform(), parseReceiverWithMode(), parseTarget(), parseTiming(), parseTransmitterWithMode(), and parseWaveform().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ collectIncludeElements() [1/2]

void serial::xml_parser_utils::collectIncludeElements ( const XmlDocument doc,
const fs::path &  currentDir,
std::vector< fs::path > &  includePaths 
)

Definition at line 1390 of file xml_parser_utils.cpp.

1391 {
1392 unsigned index = 0;
1393 while (true)
1394 {
1395 XmlElement const include_element = doc.getRootElement().childElement("include", index++);
1396 if (!include_element.isValid())
1397 break;
1398
1399 std::string const include_filename = include_element.getText();
1400 if (include_filename.empty())
1401 {
1402 LOG(logging::Level::ERROR, "<include> element is missing the filename!");
1403 continue;
1404 }
1405
1406 fs::path const include_path = currentDir / include_filename;
1407 includePaths.push_back(include_path);
1408
1410 if (!included_doc.loadFile(include_path.string()))
1411 {
1412 LOG(logging::Level::ERROR, "Failed to load included XML file: {}", include_path.string());
1413 continue;
1414 }
1415
1416 collectIncludeElements(included_doc, include_path.parent_path(), includePaths);
1417 }
1418 }
Class representing a node in an XML document.
@ ERROR
Error level for error events.

References collectIncludeElements(), logging::ERROR, LOG, and max.

Referenced by addIncludeFilesToMainDocument(), and collectIncludeElements().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ collectIncludeElements() [2/2]

void serial::xml_parser_utils::collectIncludeElements ( const XmlDocument doc,
const std::filesystem::path &  currentDir,
std::vector< std::filesystem::path > &  includePaths 
)

Recursively finds all <include> tags in a document and resolves their absolute paths.

Parameters
docThe XML document to search.
currentDirThe base directory for resolving relative paths.
includePathsA vector populated with the absolute paths of included files.

◆ createDefaultAssetLoaders()

AssetLoaders serial::xml_parser_utils::createDefaultAssetLoaders ( )

Creates an AssetLoaders struct populated with standard file-I/O implementations.

Provides the default hooks to load actual HDF5, XML, and binary waveform files from the filesystem into the simulation environment.

Returns
An AssetLoaders instance with standard file handlers attached.

Definition at line 1562 of file xml_parser_utils.cpp.

1563 {
1564 return {.loadWaveform = [](const std::string& name, const fs::path& waveform_path, RealType power,
1566 { return serial::loadWaveformFromFile(name, waveform_path.string(), power, carrierFreq, id, kind); },
1567 .loadXmlAntenna = [](const std::string& name, const std::string& filename, SimId id)
1568 { return std::make_unique<antenna::XmlAntenna>(name, filename, id); },
1569 .loadH5Antenna = [](const std::string& name, const std::string& filename, SimId id)
1570 { return std::make_unique<antenna::H5Antenna>(name, filename, id); },
1571 .loadFileTarget = [](radar::Platform* platform, const std::string& name, const std::string& filename,
1572 unsigned seed, SimId id)
1573 { return radar::createFileTarget(platform, name, filename, seed, id); }};
1574 }
Represents a simulation platform with motion and rotation paths.
Definition platform.h:32
double RealType
Type for real numbers.
Definition config.h:27
FileWaveformKind
Simulation mode assigned to samples loaded from a waveform file.
std::unique_ptr< Target > createFileTarget(Platform *platform, std::string name, const std::string &filename, unsigned seed, const SimId id=0)
Creates a file-based target.
Definition target.h:297
std::unique_ptr< RadarSignal > loadWaveformFromFile(const std::string &name, const std::string &filename, const RealType power, const RealType carrierFreq, const SimId id, const FileWaveformKind kind)

References radar::createFileTarget(), serial::xml_parser_utils::AssetLoaders::loadWaveform, serial::loadWaveformFromFile(), and max.

Referenced by serial::parseSimulation(), and serial::parseSimulationFromString().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_attribute_bool()

bool serial::xml_parser_utils::get_attribute_bool ( const XmlElement element,
const std::string &  attributeName,
bool  defaultVal 
)

Extracts a boolean value from a named attribute.

Parameters
elementThe XML element containing the attribute.
attributeNameThe name of the attribute.
defaultValThe value to return if the attribute is missing or invalid.
Returns
The parsed boolean value, or the default if the attribute is missing or invalid.

Definition at line 338 of file xml_parser_utils.cpp.

339 {
341 if (!attr_value.has_value())
342 {
343 LOG(logging::Level::DEBUG, "Attribute '{}' not specified. Defaulting to {}.", attributeName, defaultVal);
344 return defaultVal;
345 }
346 if (*attr_value == "true")
347 {
348 return true;
349 }
350 if (*attr_value == "false")
351 {
352 return false;
353 }
354
355 LOG(logging::Level::WARNING, "Invalid boolean value '{}' for attribute '{}'. Defaulting to {}.", *attr_value,
357 return defaultVal;
358 }
static std::optional< std::string > getOptionalAttribute(const XmlElement &element, const std::string_view name)
Get the value of an optional attribute.
@ WARNING
Warning level for potentially harmful situations.
@ DEBUG
Debug level for general debugging information.

References logging::DEBUG, XmlElement::getOptionalAttribute(), LOG, max, and logging::WARNING.

Referenced by parseReceiverWithMode(), and parseTiming().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_child_real_type()

RealType serial::xml_parser_utils::get_child_real_type ( const XmlElement element,
const std::string &  elementName 
)

Extracts a floating-point (RealType) value from a named child element.

Parameters
elementThe parent XML element.
elementNameThe name of the child element to extract text from.
Returns
The parsed floating-point value.
Exceptions
XmlExceptionif the child element is missing or empty.

Definition at line 328 of file xml_parser_utils.cpp.

329 {
330 const std::string text = element.childElement(elementName, 0).getText();
331 if (text.empty())
332 {
333 throw XmlException("Element " + elementName + " is empty!");
334 }
335 return std::stod(text);
336 }

References max.

Referenced by parseAntenna(), parseFixedRotation(), parseMotionPath(), parseOptionalNumericParameters(), parseParameters(), parseReceiverWithMode(), parseRotationPath(), parseTarget(), parseTiming(), parseTransmitterWithMode(), parseWaveform(), setOptionalRealParameter(), and setOptionalUnsignedParameter().

+ Here is the caller graph for this function:

◆ parseAntenna()

void serial::xml_parser_utils::parseAntenna ( const XmlElement antenna,
ParserContext ctx 
)

Parses an <antenna> block and adds it to the World.

Parameters
antennaThe <antenna> XML element.
ctxThe current parser context.

Definition at line 847 of file xml_parser_utils.cpp.

848 {
849 const std::string name = XmlElement::getSafeAttribute(antenna, "name");
850 const SimId id = assign_id_from_attribute("antenna '" + name + "'", ObjectType::Antenna);
851 const std::string pattern = XmlElement::getSafeAttribute(antenna, "pattern");
852
853 std::unique_ptr<antenna::Antenna> ant;
854
855 LOG(logging::Level::DEBUG, "Adding antenna '{}' with pattern '{}'", name, pattern);
856 if (pattern == "isotropic")
857 {
858 ant = std::make_unique<antenna::Isotropic>(name, id);
859 }
860 else if (pattern == "sinc")
861 {
862 ant = std::make_unique<antenna::Sinc>(name, get_child_real_type(antenna, "alpha"),
863 get_child_real_type(antenna, "beta"),
864 get_child_real_type(antenna, "gamma"), id);
865 }
866 else if (pattern == "gaussian")
867 {
868 ant = std::make_unique<antenna::Gaussian>(name, get_child_real_type(antenna, "azscale"),
869 get_child_real_type(antenna, "elscale"), id);
870 }
871 else if (pattern == "squarehorn")
872 {
873 ant = std::make_unique<antenna::SquareHorn>(name, get_child_real_type(antenna, "diameter"), id);
874 }
875 else if (pattern == "parabolic")
876 {
877 ant = std::make_unique<antenna::Parabolic>(name, get_child_real_type(antenna, "diameter"), id);
878 }
879 else if (pattern == "xml")
880 {
881 ant = ctx.loaders.loadXmlAntenna(name, XmlElement::getSafeAttribute(antenna, "filename"), id);
882 }
883 else if (pattern == "file")
884 {
885 ant = ctx.loaders.loadH5Antenna(name, XmlElement::getSafeAttribute(antenna, "filename"), id);
886 }
887 else
888 {
889 LOG(logging::Level::FATAL, "Unsupported antenna pattern: {}", pattern);
890 throw XmlException("Unsupported antenna pattern: " + pattern);
891 }
892
893 if (!antenna.childElement("efficiency", 0).isValid())
894 {
895 LOG(logging::Level::DEBUG, "Antenna '{}' does not specify efficiency, assuming unity.", name);
896 }
897 else
898 {
899 try
900 {
901 ant->setEfficiencyFactor(get_child_real_type(antenna, "efficiency"));
902 }
903 catch (const XmlException&)
904 {
905 LOG(logging::Level::WARNING, "Antenna '{}' has an empty efficiency, assuming unity.", name);
906 }
907 }
908
909 ctx.world->add(std::move(ant));
910 }
static std::string getSafeAttribute(const XmlElement &element, const std::string_view name)
Get the value of an attribute safely.
@ FATAL
Fatal level for severe error events.
SimId assign_id_from_attribute(const std::string &owner, ObjectType type)
Generates a unique SimId based on the requested object type.
RealType get_child_real_type(const XmlElement &element, const std::string &elementName)
Extracts a floating-point (RealType) value from a named child element.

References Antenna, assign_id_from_attribute(), logging::DEBUG, logging::FATAL, get_child_real_type(), XmlElement::getSafeAttribute(), LOG, max, and logging::WARNING.

Referenced by processParsedDocument().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseCoordinateSystemParameter()

void serial::xml_parser_utils::parseCoordinateSystemParameter ( const XmlElement parameters,
params::Parameters params_out,
const bool  origin_set 
)

Definition at line 580 of file xml_parser_utils.cpp.

582 {
583 if (const XmlElement cs_element = parameters.childElement("coordinatesystem", 0); cs_element.isValid())
584 {
585 try
586 {
587 const std::string frame_str = XmlElement::getSafeAttribute(cs_element, "frame");
588 if (frame_str == "UTM")
589 {
591 }
592 else if (frame_str == "ECEF")
593 {
594 params_out.coordinate_frame = params::CoordinateFrame::ECEF;
595 LOG(logging::Level::INFO, "KML coordinate system set to ECEF.");
596 }
597 else if (frame_str == "ENU")
598 {
599 params_out.coordinate_frame = params::CoordinateFrame::ENU;
600 if (!origin_set)
601 {
603 "ENU KML frame specified but no <origin> tag found. Using default KML origin at UCT.");
604 }
605 LOG(logging::Level::INFO, "KML coordinate system set to ENU local tangent plane.");
606 }
607 else
608 {
609 throw XmlException("Unsupported KML coordinate frame: " + frame_str);
610 }
611 }
612 catch (const std::exception& e)
613 {
615 "Could not parse KML <coordinatesystem> from XML: {}. Defaulting KML export to ENU.", e.what());
616 params_out.coordinate_frame = params::CoordinateFrame::ENU;
617 params_out.utm_zone = 0;
618 params_out.utm_north_hemisphere = true;
619 }
620 }
621 }
XmlElement childElement(const std::string_view name="", const unsigned index=0) const noexcept
Retrieve a child element by name and index.
@ INFO
Info level for informational messages.
@ ENU
East-North-Up local tangent plane (default)
@ ECEF
Earth-Centered, Earth-Fixed.
void parseUtmCoordinateSystem(const XmlElement &cs_element, params::Parameters &params_out)

References XmlElement::childElement(), params::ECEF, params::ENU, XmlElement::getSafeAttribute(), logging::INFO, LOG, max, parseUtmCoordinateSystem(), and logging::WARNING.

Referenced by parseParameters().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseFixedRotation()

void serial::xml_parser_utils::parseFixedRotation ( const XmlElement rotation,
radar::Platform platform,
params::RotationAngleUnit  unit 
)

Parses a <fixedrotation> block and attaches it to a Platform.

Parameters
rotationThe <fixedrotation> XML element.
platformThe platform to modify.

Definition at line 1040 of file xml_parser_utils.cpp.

1041 {
1042 math::RotationPath* path = platform->getRotationPath();
1043 try
1044 {
1045 const RealType start_az_deg = get_child_real_type(rotation, "startazimuth");
1046 const RealType start_el_deg = get_child_real_type(rotation, "startelevation");
1047 const RealType rate_az_deg_s = get_child_real_type(rotation, "azimuthrate");
1048 const RealType rate_el_deg_s = get_child_real_type(rotation, "elevationrate");
1049 const std::string owner = std::format("platform '{}' fixedrotation", platform->getName());
1050
1051 rotation_warning_utils::maybe_warn_about_rotation_value(
1052 start_az_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "startazimuth");
1053 rotation_warning_utils::maybe_warn_about_rotation_value(
1054 start_el_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "startelevation");
1055 rotation_warning_utils::maybe_warn_about_rotation_value(
1056 rate_az_deg_s, unit, rotation_warning_utils::ValueKind::Rate, "XML", owner, "azimuthrate");
1057 rotation_warning_utils::maybe_warn_about_rotation_value(
1058 rate_el_deg_s, unit, rotation_warning_utils::ValueKind::Rate, "XML", owner, "elevationrate");
1059 const math::RotationCoord start =
1060 rotation_angle_utils::external_rotation_to_internal(start_az_deg, start_el_deg, 0.0, unit);
1061 const math::RotationCoord rate =
1062 rotation_angle_utils::external_rotation_rate_to_internal(rate_az_deg_s, rate_el_deg_s, 0.0, unit);
1063
1064 path->setConstantRate(start, rate);
1065 LOG(logging::Level::DEBUG, "Added fixed rotation to platform {}", platform->getName());
1066 }
1067 catch (XmlException& e)
1068 {
1069 LOG(logging::Level::FATAL, "Failed to set fixed rotation for platform {}. {}", platform->getName(),
1070 e.what());
1071 throw XmlException("Failed to set fixed rotation for platform " + platform->getName());
1072 }
1073 }
Manages rotational paths with different interpolation techniques.
void setConstantRate(const RotationCoord &setstart, const RotationCoord &setrate) noexcept
Sets constant rate interpolation.
Represents a rotation in terms of azimuth, elevation, and time.
Definition coord.h:72

References serial::rotation_warning_utils::Angle, logging::DEBUG, serial::rotation_angle_utils::external_rotation_rate_to_internal(), serial::rotation_angle_utils::external_rotation_to_internal(), logging::FATAL, get_child_real_type(), LOG, max, serial::rotation_warning_utils::maybe_warn_about_rotation_value(), serial::rotation_warning_utils::Rate, and math::RotationPath::setConstantRate().

Referenced by parsePlatform().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseMonostatic()

void serial::xml_parser_utils::parseMonostatic ( const XmlElement monostatic,
radar::Platform platform,
ParserContext ctx,
const ReferenceLookup refs 
)

Parses a <monostatic> block, creating a linked transmitter and receiver pair.

Parameters
monostaticThe <monostatic> XML element.
platformThe platform this radar belongs to.
ctxThe current parser context.
refsLookup tables for resolving references.

Definition at line 1244 of file xml_parser_utils.cpp.

1246 {
1247 const std::string name = XmlElement::getSafeAttribute(monostatic, "name");
1248 const radar::OperationMode monostatic_mode = parse_mode_elements(monostatic, "Monostatic '" + name + "'");
1249 reject_non_empty_sfcw_mode(monostatic, "Monostatic '" + name + "'");
1252 if (trans->getMode() != monostatic_mode || recv->getMode() != monostatic_mode)
1253 {
1254 throw XmlException("Monostatic '" + name + "' parsed inconsistent transmitter/receiver modes.");
1255 }
1256 if (trans->getSignal() != nullptr)
1257 {
1258 validate_waveform_mode_match(*trans->getSignal(), trans->getMode(),
1259 "Monostatic '" + trans->getName() + "'");
1260 }
1261 trans->setAttached(recv);
1262 recv->setAttached(trans);
1263 }
Manages radar signal reception and response processing.
Definition receiver.h:47
Represents a radar transmitter system.
Definition transmitter.h:34
OperationMode
Defines the operational mode of a radar component.
Definition radar_obj.h:39
static radar::Transmitter * parseTransmitterWithMode(const XmlElement &transmitter, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs, const radar::OperationMode mode)
Parses a transmitter after its operation mode has already been determined.
static radar::Receiver * parseReceiverWithMode(const XmlElement &receiver, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs, const radar::OperationMode mode)
Parses a receiver after its operation mode has already been determined.

References XmlElement::getSafeAttribute(), max, parseReceiverWithMode(), and parseTransmitterWithMode().

Referenced by parsePlatformElements().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseMotionPath()

void serial::xml_parser_utils::parseMotionPath ( const XmlElement motionPath,
radar::Platform platform 
)

Parses a <motionpath> block and attaches it to a Platform.

Parameters
motionPathThe <motionpath> XML element.
platformThe platform to modify.

Definition at line 912 of file xml_parser_utils.cpp.

913 {
914 math::Path* path = platform->getMotionPath();
915 if (const auto interp_value = XmlElement::getOptionalAttribute(motionPath, "interpolation"))
916 {
917 if (*interp_value == "linear")
918 {
920 }
921 else if (*interp_value == "cubic")
922 {
924 }
925 else if (*interp_value == "static")
926 {
928 }
929 else
930 {
931 LOG(logging::Level::ERROR, "Unsupported interpolation type: {} for platform {}. Defaulting to static",
932 *interp_value, platform->getName());
934 }
935 }
936 else
937 {
939 "MotionPath interpolation type for platform {} not specified. Defaulting to static.",
940 platform->getName());
942 }
943
944 unsigned waypoint_index = 0;
945 while (true)
946 {
947 XmlElement const waypoint = motionPath.childElement("positionwaypoint", waypoint_index);
948 if (!waypoint.isValid())
949 {
950 break;
951 }
952
953 try
954 {
958 get_child_real_type(waypoint, "altitude"));
959 path->addCoord(coord);
960 LOG(logging::Level::TRACE, "Added waypoint {} to motion path for platform {}.", waypoint_index,
961 platform->getName());
962 }
963 catch (const XmlException& e)
964 {
965 LOG(logging::Level::ERROR, "Failed to add waypoint to motion path. Discarding waypoint. {}", e.what());
966 }
967
969 }
970 path->finalize();
971 }
Represents a path with coordinates and allows for various interpolation methods.
Definition path.h:31
@ INTERP_STATIC
Hold the first coordinate for all query times.
@ INTERP_LINEAR
Linearly interpolate between neighboring coordinates.
@ INTERP_CUBIC
Cubically interpolate between neighboring coordinates.
void setInterp(InterpType settype) noexcept
Changes the interpolation type.
Definition path.cpp:164
void addCoord(const Coord &coord) noexcept
Adds a coordinate to the path.
Definition path.cpp:27
void finalize()
Finalizes the path, preparing it for interpolation.
Definition path.cpp:147
A class representing a vector in rectangular coordinates.
Represents a position in 3D space with an associated time.
Definition coord.h:24

References math::Path::addCoord(), logging::DEBUG, logging::ERROR, math::Path::finalize(), get_child_real_type(), XmlElement::getOptionalAttribute(), math::Path::INTERP_CUBIC, math::Path::INTERP_LINEAR, math::Path::INTERP_STATIC, LOG, max, math::Path::setInterp(), and logging::TRACE.

Referenced by parsePlatform().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseOptionalNumericParameters()

void serial::xml_parser_utils::parseOptionalNumericParameters ( const XmlElement parameters,
params::Parameters params_out 
)

Definition at line 460 of file xml_parser_utils.cpp.

461 {
463 [&](const RealType value)
464 {
465 params_out.c = value;
466 LOG(logging::Level::INFO, "Propagation speed (c) set to: {:.5f}", value);
467 });
468
469 setOptionalRealParameter(parameters, "simSamplingRate", 1000.0,
470 [&](const RealType value)
471 {
472 params_out.sim_sampling_rate = value;
473 LOG(logging::Level::DEBUG, "Simulation sampling rate set to: {:.5f} Hz", value);
474 });
475
476 if (parameters.childElement("randomseed", 0).isValid())
477 {
478 const auto seed = parseUnsignedParameter("randomseed", get_child_real_type(parameters, "randomseed"));
479 params_out.random_seed = seed;
480 LOG(logging::Level::DEBUG, "Random seed set to: {}", seed);
481 }
482
483 setOptionalUnsignedParameter(parameters, "adc_bits", 0,
484 [&](const unsigned value)
485 {
486 params_out.adc_bits = value;
487 LOG(logging::Level::DEBUG, "ADC quantization bits set to: {}", value);
488 });
489
490 setOptionalUnsignedParameter(parameters, "oversample", 1,
491 [&](const unsigned value)
492 {
494 params_out.oversample_ratio = value;
495 LOG(logging::Level::DEBUG, "Oversampling enabled with ratio: {}", value);
496 });
497 }
bool isValid() const noexcept
Check if the XML element is valid.
void validateOversampleRatio(const unsigned ratio)
Validates that an oversampling ratio is supported.
Definition parameters.h:164
void setOptionalUnsignedParameter(const XmlElement &parameters, const std::string &param_name, const unsigned default_value, Setter setter)
void setOptionalRealParameter(const XmlElement &parameters, const std::string &param_name, const RealType default_value, Setter setter)
static constexpr RealType DEFAULT_C
Speed of light (m/s)
Definition parameters.h:53

References XmlElement::childElement(), logging::DEBUG, params::Parameters::DEFAULT_C, get_child_real_type(), logging::INFO, XmlElement::isValid(), LOG, max, parseUnsignedParameter(), setOptionalRealParameter(), setOptionalUnsignedParameter(), and params::validateOversampleRatio().

Referenced by parseParameters().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseOriginParameter()

bool serial::xml_parser_utils::parseOriginParameter ( const XmlElement parameters,
params::Parameters params_out 
)

Definition at line 521 of file xml_parser_utils.cpp.

522 {
523 bool origin_set = false;
524 if (const XmlElement origin_element = parameters.childElement("origin", 0); origin_element.isValid())
525 {
526 try
527 {
528 params_out.origin_latitude = std::stod(XmlElement::getSafeAttribute(origin_element, "latitude"));
529 params_out.origin_longitude = std::stod(XmlElement::getSafeAttribute(origin_element, "longitude"));
530 if (const auto altitude = XmlElement::getOptionalAttribute(origin_element, "altitude"))
531 {
532 params_out.origin_altitude = std::stod(*altitude);
533 }
534 else
535 {
536 params_out.origin_altitude = 0.0;
537 LOG(logging::Level::DEBUG, "KML origin altitude not specified. Defaulting to 0.");
538 }
539 origin_set = true;
540 LOG(logging::Level::INFO, "KML origin set to lat: {}, lon: {}, alt: {}", params_out.origin_latitude,
541 params_out.origin_longitude, params_out.origin_altitude);
542 }
543 catch (const std::exception& e)
544 {
545 LOG(logging::Level::WARNING, "Could not parse KML origin from XML, using defaults. Error: {}",
546 e.what());
547 }
548 }
549 return origin_set;
550 }

References XmlElement::childElement(), logging::DEBUG, XmlElement::getOptionalAttribute(), XmlElement::getSafeAttribute(), logging::INFO, LOG, max, and logging::WARNING.

Referenced by parseParameters().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseParameters()

void serial::xml_parser_utils::parseParameters ( const XmlElement parameters,
params::Parameters params_out 
)

Parses the <parameters> block into the isolated context parameters.

Parameters
parametersThe <parameters> XML element.
params_outThe Parameters struct to mutate with parsed values.

Definition at line 623 of file xml_parser_utils.cpp.

624 {
625 params_out.start = get_child_real_type(parameters, "starttime");
626 params_out.end = get_child_real_type(parameters, "endtime");
627 LOG(logging::Level::INFO, "Simulation time set from {:.5f} to {:.5f} seconds", params_out.start,
628 params_out.end);
629
630 params_out.rate = get_child_real_type(parameters, "rate");
631 if (params_out.rate <= 0)
632 {
633 throw std::runtime_error("Sampling rate must be > 0");
634 }
635 LOG(logging::Level::DEBUG, "Sample rate set to: {:.5f}", params_out.rate);
636
637 parseOptionalNumericParameters(parameters, params_out);
638 parseRotationAngleUnit(parameters, params_out);
639 const bool origin_set = parseOriginParameter(parameters, params_out);
640 parseCoordinateSystemParameter(parameters, params_out, origin_set);
641 }

References logging::DEBUG, get_child_real_type(), logging::INFO, LOG, max, parseCoordinateSystemParameter(), parseOptionalNumericParameters(), parseOriginParameter(), and parseRotationAngleUnit().

Referenced by processParsedDocument().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parsePlatform()

void serial::xml_parser_utils::parsePlatform ( const XmlElement platform,
ParserContext ctx,
const std::function< void(const XmlElement &, std::string_view)> &  register_name,
const ReferenceLookup refs 
)

Parses a complete <platform> block, including its motion paths and sub-elements.

Parameters
platformThe <platform> XML element.
ctxThe current parser context.
register_nameCallback used to ensure unique naming.
refsLookup tables for resolving references.

Definition at line 1353 of file xml_parser_utils.cpp.

1356 {
1357 std::string const name = XmlElement::getSafeAttribute(platform, "name");
1358 const SimId id = assign_id_from_attribute("platform '" + name + "'", ObjectType::Platform);
1359 auto plat = std::make_unique<radar::Platform>(name, id);
1360
1362
1363 if (const XmlElement motion_path = platform.childElement("motionpath", 0); motion_path.isValid())
1364 {
1366 }
1367
1368 const XmlElement rot_path = platform.childElement("rotationpath", 0);
1369 if (const XmlElement fixed_rot = platform.childElement("fixedrotation", 0);
1370 rot_path.isValid() && fixed_rot.isValid())
1371 {
1373 "Both <rotationpath> and <fixedrotation> are declared for platform {}. Only <rotationpath> will be "
1374 "used.",
1375 plat->getName());
1376 parseRotationPath(rot_path, plat.get(), ctx.parameters.rotation_angle_unit);
1377 }
1378 else if (rot_path.isValid())
1379 {
1380 parseRotationPath(rot_path, plat.get(), ctx.parameters.rotation_angle_unit);
1381 }
1382 else if (fixed_rot.isValid())
1383 {
1384 parseFixedRotation(fixed_rot, plat.get(), ctx.parameters.rotation_angle_unit);
1385 }
1386
1387 ctx.world->add(std::move(plat));
1388 }
void parseFixedRotation(const XmlElement &rotation, radar::Platform *platform, const params::RotationAngleUnit unit)
Parses a <fixedrotation> block and attaches it to a Platform.
void parsePlatformElements(const XmlElement &platform, ParserContext &ctx, radar::Platform *plat, const std::function< void(const XmlElement &, std::string_view)> &register_name, const ReferenceLookup &refs)
Iterates and parses all children elements (radars, targets) of a platform.
void parseMotionPath(const XmlElement &motionPath, radar::Platform *platform)
Parses a <motionpath> block and attaches it to a Platform.

References assign_id_from_attribute(), logging::ERROR, XmlElement::getSafeAttribute(), LOG, max, parseFixedRotation(), parseMotionPath(), parsePlatformElements(), parseRotationPath(), and Platform.

Referenced by processParsedDocument().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parsePlatformElements()

void serial::xml_parser_utils::parsePlatformElements ( const XmlElement platform,
ParserContext ctx,
radar::Platform plat,
const std::function< void(const XmlElement &, std::string_view)> &  register_name,
const ReferenceLookup refs 
)

Iterates and parses all children elements (radars, targets) of a platform.

Parameters
platformThe <platform> XML element.
ctxThe current parser context.
platThe Platform object to attach parsed elements to.
register_nameCallback used to ensure unique naming globally across parsed objects.
refsLookup tables for resolving references.

Definition at line 1317 of file xml_parser_utils.cpp.

1320 {
1321 auto parseChildrenWithRefs = [&](const std::string& elementName, auto parseFunc)
1322 {
1323 unsigned index = 0;
1324 while (true)
1325 {
1326 const XmlElement element = platform.childElement(elementName, index++);
1327 if (!element.isValid())
1328 break;
1331 }
1332 };
1333
1334 auto parseChildrenWithoutRefs = [&](const std::string& elementName, auto parseFunc)
1335 {
1336 unsigned index = 0;
1337 while (true)
1338 {
1339 const XmlElement element = platform.childElement(elementName, index++);
1340 if (!element.isValid())
1341 break;
1344 }
1345 };
1346
1347 parseChildrenWithRefs("monostatic", parseMonostatic);
1348 parseChildrenWithRefs("transmitter", parseTransmitter);
1349 parseChildrenWithRefs("receiver", parseReceiver);
1350 parseChildrenWithoutRefs("target", parseTarget);
1351 }

References max, parseMonostatic(), parseReceiver(), parseTarget(), and parseTransmitter().

Referenced by parsePlatform().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseReceiver()

radar::Receiver * serial::xml_parser_utils::parseReceiver ( const XmlElement receiver,
radar::Platform platform,
ParserContext ctx,
const ReferenceLookup refs 
)

Parses a <receiver> block, resolves its dependencies, and adds it to the World.

Parameters
receiverThe <receiver> XML element.
platformThe platform this receiver belongs to.
ctxThe current parser context.
refsLookup tables for resolving antenna and timing references.
Returns
A pointer to the newly created Receiver object.

Definition at line 1235 of file xml_parser_utils.cpp.

1237 {
1238 const std::string name = XmlElement::getSafeAttribute(receiver, "name");
1239 const radar::OperationMode mode = parse_mode_elements(receiver, "Receiver '" + name + "'");
1240 reject_non_empty_sfcw_mode(receiver, "Receiver '" + name + "'");
1242 }
const Receiver & receiver

References XmlElement::getSafeAttribute(), max, parseReceiverWithMode(), and receiver.

Referenced by parsePlatformElements().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseReceiverWithMode()

static radar::Receiver * serial::xml_parser_utils::parseReceiverWithMode ( const XmlElement receiver,
radar::Platform platform,
ParserContext ctx,
const ReferenceLookup refs,
const radar::OperationMode  mode 
)
static

Parses a receiver after its operation mode has already been determined.

Definition at line 1148 of file xml_parser_utils.cpp.

1151 {
1152 const std::string name = XmlElement::getSafeAttribute(receiver, "name");
1153 const SimId id = assign_id_from_attribute("receiver '" + name + "'", ObjectType::Receiver);
1154 const XmlElement pulsed_mode_element = receiver.childElement("pulsed_mode", 0);
1155 const bool is_pulsed = mode == radar::OperationMode::PULSED_MODE;
1156
1157 auto receiver_obj = std::make_unique<radar::Receiver>(platform, name, next_seed(*ctx.master_seeder), mode, id);
1158
1159 const SimId ant_id = resolve_reference_id(receiver, "antenna", "receiver '" + name + "'", *refs.antennas);
1160 const antenna::Antenna* antenna = ctx.world->findAntenna(ant_id);
1161 if (antenna == nullptr)
1162 {
1163 throw XmlException("Antenna ID '" + std::to_string(ant_id) + "' not found for receiver '" + name + "'");
1164 }
1165 receiver_obj->setAntenna(antenna);
1166
1167 if (!receiver.childElement("noise_temp", 0).isValid())
1168 {
1169 LOG(logging::Level::DEBUG, "Receiver '{}' does not specify noise temperature",
1170 receiver_obj->getName().c_str());
1171 }
1172 else
1173 {
1174 try
1175 {
1176 receiver_obj->setNoiseTemperature(get_child_real_type(receiver, "noise_temp"));
1177 }
1178 catch (const XmlException&)
1179 {
1180 LOG(logging::Level::WARNING, "Receiver '{}' has an empty noise temperature; using default.",
1181 receiver_obj->getName().c_str());
1182 }
1183 }
1184
1185 if (is_pulsed)
1186 {
1187 const RealType window_length = get_child_real_type(pulsed_mode_element, "window_length");
1188 if (window_length <= 0)
1189 {
1190 throw XmlException("<window_length> must be positive for receiver '" + name + "'");
1191 }
1192
1194 if (prf <= 0)
1195 {
1196 throw XmlException("<prf> must be positive for receiver '" + name + "'");
1197 }
1198
1199 const RealType window_skip = get_child_real_type(pulsed_mode_element, "window_skip");
1200 if (window_skip < 0)
1201 {
1202 throw XmlException("<window_skip> must not be negative for receiver '" + name + "'");
1203 }
1204 receiver_obj->setWindowProperties(window_length, prf, window_skip);
1205 }
1206 const SimId timing_id = resolve_reference_id(receiver, "timing", "receiver '" + name + "'", *refs.timings);
1207 receiver_obj->setTiming(resolve_timing_instance(timing_id, ctx, "receiver '" + name + "'"));
1208
1209 if (get_attribute_bool(receiver, "nodirect", false))
1210 {
1212 LOG(logging::Level::DEBUG, "Ignoring direct signals for receiver '{}'", receiver_obj->getName().c_str());
1213 }
1214
1215 if (get_attribute_bool(receiver, "nopropagationloss", false))
1216 {
1218 LOG(logging::Level::DEBUG, "Ignoring propagation losses for receiver '{}'",
1219 receiver_obj->getName().c_str());
1220 }
1221
1222 RealType const pri = is_pulsed ? (1.0 / receiver_obj->getWindowPrf()) : 0.0;
1224 if (!schedule.empty())
1225 {
1226 receiver_obj->setSchedule(std::move(schedule));
1227 }
1228
1229 parse_receiver_dechirp_config(receiver, *receiver_obj, "Receiver '" + name + "'");
1230
1231 ctx.world->add(std::move(receiver_obj));
1232 return ctx.world->getReceivers().back().get();
1233 }
Abstract base class representing an antenna.
@ FLAG_NODIRECT
Disable direct-path reception.
@ FLAG_NOPROPLOSS
Disable propagation-loss scaling.
@ PULSED_MODE
The component operates in a pulsed mode.
std::vector< radar::SchedulePeriod > parseSchedule(const XmlElement &parent, const std::string &parentName, const bool isPulsed, const RealType pri)
Parses a schedule (active periods) for a transmitter or receiver.
SimId resolve_reference_id(const XmlElement &element, const std::string &attributeName, const std::string &owner, const std::unordered_map< std::string, SimId > &name_map)
Resolves an XML string reference into an internal SimId.
bool get_attribute_bool(const XmlElement &element, const std::string &attributeName, const bool defaultVal)
Extracts a boolean value from a named attribute.

References assign_id_from_attribute(), logging::DEBUG, radar::Receiver::FLAG_NODIRECT, radar::Receiver::FLAG_NOPROPLOSS, get_attribute_bool(), get_child_real_type(), XmlElement::getSafeAttribute(), LOG, max, parseSchedule(), radar::PULSED_MODE, Receiver, receiver, resolve_reference_id(), and logging::WARNING.

Referenced by parseMonostatic(), and parseReceiver().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseRotationAngleUnit()

void serial::xml_parser_utils::parseRotationAngleUnit ( const XmlElement parameters,
params::Parameters params_out 
)

Definition at line 499 of file xml_parser_utils.cpp.

500 {
501 try
502 {
503 const auto unit_token = parameters.childElement("rotationangleunit", 0).getText();
504 if (!unit_token.empty())
505 {
506 if (const auto unit = params::rotationAngleUnitFromToken(unit_token))
507 {
508 params_out.rotation_angle_unit = *unit;
509 }
510 else
511 {
512 throw XmlException("Unsupported rotation angle unit '" + unit_token + "'.");
513 }
514 }
515 }
516 catch (const XmlException&)
517 {
518 }
519 }
std::string getText() const
Get the text content of the XML element.
std::optional< RotationAngleUnit > rotationAngleUnitFromToken(const std::string_view token) noexcept
Parses a rotation angle unit from an XML token.
Definition parameters.h:362

References XmlElement::childElement(), XmlElement::getText(), max, and params::rotationAngleUnitFromToken().

Referenced by parseParameters().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseRotationPath()

void serial::xml_parser_utils::parseRotationPath ( const XmlElement rotation,
radar::Platform platform,
params::RotationAngleUnit  unit 
)

Parses a <rotationpath> block and attaches it to a Platform.

Parameters
rotationThe <rotationpath> XML element.
platformThe platform to modify.

Definition at line 973 of file xml_parser_utils.cpp.

974 {
975 math::RotationPath* path = platform->getRotationPath();
976 try
977 {
978 if (const std::string interp = XmlElement::getSafeAttribute(rotation, "interpolation"); interp == "linear")
979 {
981 }
982 else if (interp == "cubic")
983 {
985 }
986 else if (interp == "static")
987 {
989 }
990 else
991 {
992 throw XmlException("Unsupported interpolation type: " + interp);
993 }
994 }
995 catch (XmlException&)
996 {
998 "Failed to set RotationPath interpolation type for platform {}. Defaulting to static",
999 platform->getName());
1001 }
1002
1003 unsigned waypoint_index = 0;
1004 while (true)
1005 {
1006 XmlElement const waypoint = rotation.childElement("rotationwaypoint", waypoint_index);
1007 if (!waypoint.isValid())
1008 {
1009 break;
1010 }
1011
1012 try
1013 {
1014 LOG(logging::Level::TRACE, "Adding waypoint {} to rotation path for platform {}.", waypoint_index,
1015 platform->getName());
1016
1017 const RealType az_deg = get_child_real_type(waypoint, "azimuth");
1018 const RealType el_deg = get_child_real_type(waypoint, "elevation");
1019 const RealType time = get_child_real_type(waypoint, "time");
1020 const std::string owner =
1021 std::format("platform '{}' rotation waypoint {}", platform->getName(), waypoint_index);
1022
1023 rotation_warning_utils::maybe_warn_about_rotation_value(
1024 az_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "azimuth");
1025 rotation_warning_utils::maybe_warn_about_rotation_value(
1026 el_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "elevation");
1027
1028 path->addCoord(rotation_angle_utils::external_rotation_to_internal(az_deg, el_deg, time, unit));
1029 }
1030 catch (const XmlException& e)
1031 {
1032 LOG(logging::Level::ERROR, "Failed to add waypoint to rotation path. Discarding waypoint. {}",
1033 e.what());
1034 }
1036 }
1037 path->finalize();
1038 }
void finalize()
Finalizes the rotation path for interpolation.
void setInterp(InterpType setinterp) noexcept
Sets the interpolation type for the path.
void addCoord(const RotationCoord &coord) noexcept
Adds a rotation coordinate to the path.
@ INTERP_STATIC
Hold the first rotation for all query times.
@ INTERP_LINEAR
Linearly interpolate between neighboring rotations.
@ INTERP_CUBIC
Cubically interpolate between neighboring rotations.

References math::RotationPath::addCoord(), serial::rotation_warning_utils::Angle, logging::ERROR, serial::rotation_angle_utils::external_rotation_to_internal(), math::RotationPath::finalize(), get_child_real_type(), XmlElement::getSafeAttribute(), math::RotationPath::INTERP_CUBIC, math::RotationPath::INTERP_LINEAR, math::RotationPath::INTERP_STATIC, LOG, max, serial::rotation_warning_utils::maybe_warn_about_rotation_value(), math::RotationPath::setInterp(), and logging::TRACE.

Referenced by parsePlatform().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseSchedule()

std::vector< radar::SchedulePeriod > serial::xml_parser_utils::parseSchedule ( const XmlElement parent,
const std::string &  parentName,
bool  isPulsed,
RealType  pri = 0.0 
)

Parses a schedule (active periods) for a transmitter or receiver.

Parameters
parentThe parent XML element that might contain a <schedule> block.
parentNameName of the parent for error logging.
isPulsedTrue if the owning object operates in pulsed mode (used for PRI validation).
priThe pulse repetition interval, if applicable.
Returns
A vector of parsed and validated SchedulePeriod objects.

Definition at line 383 of file xml_parser_utils.cpp.

385 {
386 std::vector<radar::SchedulePeriod> raw_periods;
387 if (const XmlElement schedule_element = parent.childElement("schedule", 0); schedule_element.isValid())
388 {
389 unsigned p_idx = 0;
390 while (true)
391 {
392 XmlElement const period_element = schedule_element.childElement("period", p_idx++);
393 if (!period_element.isValid())
394 {
395 break;
396 }
397 try
398 {
399 const RealType start = std::stod(XmlElement::getSafeAttribute(period_element, "start"));
400 const RealType end = std::stod(XmlElement::getSafeAttribute(period_element, "end"));
401 raw_periods.push_back({start, end});
402 }
403 catch (const std::exception& e)
404 {
405 LOG(logging::Level::WARNING, "Failed to parse schedule period for '{}': {}", parentName, e.what());
406 }
407 }
408 }
410 }
std::vector< SchedulePeriod > processRawSchedule(const std::vector< SchedulePeriod > &periods, const std::string &ownerName, const bool isPulsed, const RealType pri)
Processes a raw list of schedule periods.

References XmlElement::getSafeAttribute(), LOG, max, radar::processRawSchedule(), and logging::WARNING.

Referenced by parseReceiverWithMode(), and parseTransmitterWithMode().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseTarget()

void serial::xml_parser_utils::parseTarget ( const XmlElement target,
radar::Platform platform,
ParserContext ctx 
)

Parses a <target> block and adds it to the World.

Parameters
targetThe <target> XML element.
platformThe platform this target belongs to.
ctxThe current parser context.

Definition at line 1265 of file xml_parser_utils.cpp.

1266 {
1267 const std::string name = XmlElement::getSafeAttribute(target, "name");
1268 const SimId id = assign_id_from_attribute("target '" + name + "'", ObjectType::Target);
1269
1270 const XmlElement rcs_element = target.childElement("rcs", 0);
1271 if (!rcs_element.isValid())
1272 {
1273 throw XmlException("<rcs> element is required in <target>!");
1274 }
1275
1276 const std::string rcs_type = XmlElement::getSafeAttribute(rcs_element, "type");
1277 std::unique_ptr<radar::Target> target_obj;
1278 const unsigned seed = next_seed(*ctx.master_seeder);
1279
1280 if (rcs_type == "isotropic")
1281 {
1282 target_obj = radar::createIsoTarget(platform, name, get_child_real_type(rcs_element, "value"), seed, id);
1283 }
1284 else if (rcs_type == "file")
1285 {
1286 // Defer to dependency-injected file loader
1287 target_obj = ctx.loaders.loadFileTarget(platform, name,
1289 }
1290 else
1291 {
1292 throw XmlException("Unsupported RCS type: " + rcs_type);
1293 }
1294
1295 if (const XmlElement model = target.childElement("model", 0); model.isValid())
1296 {
1297 if (const std::string model_type = XmlElement::getSafeAttribute(model, "type"); model_type == "constant")
1298 {
1299 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
1300 }
1301 else if (model_type == "chisquare" || model_type == "gamma")
1302 {
1303 target_obj->setFluctuationModel(
1304 std::make_unique<radar::RcsChiSquare>(target_obj->getRngEngine(), get_child_real_type(model, "k")));
1305 }
1306 else
1307 {
1308 throw XmlException("Unsupported model type: " + model_type);
1309 }
1310 }
1311
1312 LOG(logging::Level::DEBUG, "Added target {} with RCS type {} to platform {}", name, rcs_type,
1313 platform->getName());
1314 ctx.world->add(std::move(target_obj));
1315 }
std::unique_ptr< Target > createIsoTarget(Platform *platform, std::string name, RealType rcs, unsigned seed, const SimId id=0)
Creates an isotropic target.
Definition target.h:282

References assign_id_from_attribute(), radar::createIsoTarget(), logging::DEBUG, get_child_real_type(), XmlElement::getSafeAttribute(), LOG, max, and Target.

Referenced by parsePlatformElements().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseTiming()

void serial::xml_parser_utils::parseTiming ( const XmlElement timing,
ParserContext ctx 
)

Parses a <timing> block and adds the prototype timing to the World.

Parameters
timingThe <timing> XML element.
ctxThe current parser context.

Definition at line 790 of file xml_parser_utils.cpp.

791 {
792 const std::string name = XmlElement::getSafeAttribute(timing, "name");
793 const SimId id = assign_id_from_attribute("timing '" + name + "'", ObjectType::Timing);
794 const RealType freq = get_child_real_type(timing, "frequency");
795 auto timing_obj = std::make_unique<timing::PrototypeTiming>(name, id);
796
797 timing_obj->setFrequency(freq);
798
799 unsigned noise_index = 0;
800 while (true)
801 {
802 XmlElement const noise_element = timing.childElement("noise_entry", noise_index++);
803 if (!noise_element.isValid())
804 {
805 break;
806 }
807
808 timing_obj->setAlpha(get_child_real_type(noise_element, "alpha"),
809 get_child_real_type(noise_element, "weight"));
810 }
811
813 [&](const std::string& element_name, const std::string& description, auto setter)
814 {
815 if (!timing.childElement(element_name, 0).isValid())
816 {
817 LOG(logging::Level::DEBUG, "Clock section '{}' does not specify {}.", name, description);
818 return;
819 }
820 try
821 {
823 }
824 catch (const XmlException&)
825 {
826 LOG(logging::Level::WARNING, "Clock section '{}' has an empty {}. Using default.", name, description);
827 }
828 };
829
830 set_optional_timing_parameter("freq_offset", "frequency offset",
831 [&](const RealType value) { timing_obj->setFreqOffset(value); });
832 set_optional_timing_parameter("random_freq_offset_stdev", "random frequency offset",
833 [&](const RealType value) { timing_obj->setRandomFreqOffsetStdev(value); });
834 set_optional_timing_parameter("phase_offset", "phase offset",
835 [&](const RealType value) { timing_obj->setPhaseOffset(value); });
836 set_optional_timing_parameter("random_phase_offset_stdev", "random phase offset",
837 [&](const RealType value) { timing_obj->setRandomPhaseOffsetStdev(value); });
838
839 if (get_attribute_bool(timing, "synconpulse", false))
840 {
841 timing_obj->setSyncOnPulse();
842 }
843
844 ctx.world->add(std::move(timing_obj));
845 }

References assign_id_from_attribute(), logging::DEBUG, get_attribute_bool(), get_child_real_type(), XmlElement::getSafeAttribute(), LOG, max, Timing, and logging::WARNING.

Referenced by processParsedDocument().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseTransmitter()

radar::Transmitter * serial::xml_parser_utils::parseTransmitter ( const XmlElement transmitter,
radar::Platform platform,
ParserContext ctx,
const ReferenceLookup refs 
)

Parses a <transmitter> block, resolves its dependencies, and adds it to the World.

Parameters
transmitterThe <transmitter> XML element.
platformThe platform this transmitter belongs to.
ctxThe current parser context.
refsLookup tables for resolving waveform, antenna, and timing references.
Returns
A pointer to the newly created Transmitter object.

Definition at line 1133 of file xml_parser_utils.cpp.

1135 {
1136 const std::string name = XmlElement::getSafeAttribute(transmitter, "name");
1137 const radar::OperationMode mode = parse_mode_elements(transmitter, "Transmitter '" + name + "'");
1138 reject_non_empty_sfcw_mode(transmitter, "Transmitter '" + name + "'");
1139 if (const XmlElement fmcw_mode = transmitter.childElement("fmcw_mode", 0);
1141 {
1142 throw XmlException("Transmitter '" + name + "' fmcw_mode must not contain dechirp configuration.");
1143 }
1144 return parseTransmitterWithMode(transmitter, platform, ctx, refs, mode);
1145 }
const Transmitter & transmitter

References XmlElement::getSafeAttribute(), max, parseTransmitterWithMode(), and transmitter.

Referenced by parsePlatformElements().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseTransmitterWithMode()

static radar::Transmitter * serial::xml_parser_utils::parseTransmitterWithMode ( const XmlElement transmitter,
radar::Platform platform,
ParserContext ctx,
const ReferenceLookup refs,
const radar::OperationMode  mode 
)
static

Parses a transmitter after its operation mode has already been determined.

Definition at line 1076 of file xml_parser_utils.cpp.

1079 {
1080 const std::string name = XmlElement::getSafeAttribute(transmitter, "name");
1081 const SimId id = assign_id_from_attribute("transmitter '" + name + "'", ObjectType::Transmitter);
1082 const XmlElement pulsed_mode_element = transmitter.childElement("pulsed_mode", 0);
1083 const bool is_pulsed = mode == radar::OperationMode::PULSED_MODE;
1084
1085 auto transmitter_obj = std::make_unique<radar::Transmitter>(platform, name, mode, id);
1086
1087 const SimId waveform_id =
1088 resolve_reference_id(transmitter, "waveform", "transmitter '" + name + "'", *refs.waveforms);
1089 fers_signal::RadarSignal* wave = ctx.world->findWaveform(waveform_id);
1090 if (wave == nullptr)
1091 {
1092 throw XmlException("Waveform ID '" + std::to_string(waveform_id) + "' not found for transmitter '" + name +
1093 "'");
1094 }
1095 validate_fmcw_waveform(*wave, "Waveform '" + wave->getName() + "'");
1096 validate_waveform_mode_match(*wave, mode, "Transmitter '" + name + "'");
1097 transmitter_obj->setWave(wave);
1098
1099 if (is_pulsed)
1100 {
1101 transmitter_obj->setPrf(get_child_real_type(pulsed_mode_element, "prf"));
1102 }
1103
1104 const SimId antenna_id =
1105 resolve_reference_id(transmitter, "antenna", "transmitter '" + name + "'", *refs.antennas);
1106 const antenna::Antenna* ant = ctx.world->findAntenna(antenna_id);
1107 if (ant == nullptr)
1108 {
1109 throw XmlException("Antenna ID '" + std::to_string(antenna_id) + "' not found for transmitter '" + name +
1110 "'");
1111 }
1112 transmitter_obj->setAntenna(ant);
1113
1114 const SimId timing_id =
1115 resolve_reference_id(transmitter, "timing", "transmitter '" + name + "'", *refs.timings);
1116 transmitter_obj->setTiming(resolve_timing_instance(timing_id, ctx, "transmitter '" + name + "'"));
1117
1118 RealType const pri = is_pulsed ? (1.0 / transmitter_obj->getPrf()) : 0.0;
1120 if (wave->isFmcwFamily() || wave->isSteppedFrequency())
1121 {
1122 validate_fmcw_schedule(schedule, *wave, "Transmitter '" + name + "'");
1123 }
1124 if (!schedule.empty())
1125 {
1126 transmitter_obj->setSchedule(std::move(schedule));
1127 }
1128
1129 ctx.world->add(std::move(transmitter_obj));
1130 return ctx.world->getTransmitters().back().get();
1131 }
Class representing a radar signal with associated properties.

References assign_id_from_attribute(), get_child_real_type(), XmlElement::getSafeAttribute(), max, parseSchedule(), radar::PULSED_MODE, resolve_reference_id(), Transmitter, and transmitter.

Referenced by parseMonostatic(), and parseTransmitter().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseUnsignedParameter()

unsigned serial::xml_parser_utils::parseUnsignedParameter ( const std::string_view  param_name,
const RealType  raw_value 
)

Definition at line 412 of file xml_parser_utils.cpp.

413 {
414 if (!std::isfinite(raw_value))
415 {
416 throw XmlException(std::format("Parameter '{}' must be finite.", param_name));
417 }
418 if (raw_value < 0.0)
419 {
420 throw XmlException(std::format("Parameter '{}' must be non-negative.", param_name));
421 }
422
423 const RealType floored_value = std::floor(raw_value);
424 if (floored_value > static_cast<RealType>(std::numeric_limits<unsigned>::max()))
425 {
426 throw XmlException(std::format("Parameter '{}' exceeds the supported unsigned range.", param_name));
427 }
428
429 return static_cast<unsigned>(floored_value);
430 }

References max.

Referenced by parseOptionalNumericParameters(), and setOptionalUnsignedParameter().

+ Here is the caller graph for this function:

◆ parseUtmCoordinateSystem()

void serial::xml_parser_utils::parseUtmCoordinateSystem ( const XmlElement cs_element,
params::Parameters params_out 
)

Definition at line 552 of file xml_parser_utils.cpp.

553 {
554 params_out.coordinate_frame = params::CoordinateFrame::UTM;
555 params_out.utm_zone = std::stoi(XmlElement::getSafeAttribute(cs_element, "zone"));
556 const std::string hem_str = XmlElement::getSafeAttribute(cs_element, "hemisphere");
557
558 if (params_out.utm_zone < GeographicLib::UTMUPS::MINUTMZONE ||
559 params_out.utm_zone > GeographicLib::UTMUPS::MAXUTMZONE)
560 {
561 throw XmlException("KML UTM zone " + std::to_string(params_out.utm_zone) +
562 " is invalid; must be in [1, 60].");
563 }
564 if (hem_str == "N" || hem_str == "n")
565 {
566 params_out.utm_north_hemisphere = true;
567 }
568 else if (hem_str == "S" || hem_str == "s")
569 {
570 params_out.utm_north_hemisphere = false;
571 }
572 else
573 {
574 throw XmlException("KML UTM hemisphere '" + hem_str + "' is invalid; must be 'N' or 'S'.");
575 }
576 LOG(logging::Level::INFO, "KML coordinate system set to UTM, zone {}{}", params_out.utm_zone,
577 params_out.utm_north_hemisphere ? 'N' : 'S');
578 }
@ UTM
Universal Transverse Mercator.

References XmlElement::getSafeAttribute(), logging::INFO, LOG, max, and params::UTM.

Referenced by parseCoordinateSystemParameter().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseWaveform()

void serial::xml_parser_utils::parseWaveform ( const XmlElement waveform,
ParserContext ctx 
)

Parses a <waveform> block and adds it to the World.

Parameters
waveformThe <waveform> XML element.
ctxThe current parser context.

Definition at line 643 of file xml_parser_utils.cpp.

644 {
645 const std::string name = XmlElement::getSafeAttribute(waveform, "name");
646 const SimId id = assign_id_from_attribute("waveform '" + name + "'", ObjectType::Waveform);
647
648 const auto power = get_child_real_type(waveform, "power");
649 const auto carrier = get_child_real_type(waveform, "carrier_frequency");
650
652 {
653 const std::string filename_str = XmlElement::getSafeAttribute(file_element, "filename");
654 fs::path waveform_path(filename_str);
655
656 if (!fs::exists(waveform_path))
657 {
658 waveform_path = ctx.base_dir / filename_str;
659 }
660
661 auto wave = ctx.loaders.loadWaveform(name, waveform_path, power, carrier, id, kind);
662 ctx.world->add(std::move(wave));
663 };
664
665 if (const XmlElement pulsed_file = waveform.childElement("pulsed_from_file", 0); pulsed_file.isValid())
666 {
668 }
669 else if (const XmlElement cw_file = waveform.childElement("cw_from_file", 0); cw_file.isValid())
670 {
672 }
673 else if (const XmlElement fmcw_file = waveform.childElement("fmcw_from_file", 0); fmcw_file.isValid())
674 {
676 }
677 else if (waveform.childElement("cw", 0).isValid())
678 {
679 auto cw_signal = std::make_unique<fers_signal::CwSignal>();
680 auto wave = std::make_unique<fers_signal::RadarSignal>(
681 name, power, carrier, ctx.parameters.end - ctx.parameters.start, std::move(cw_signal), id);
682 ctx.world->add(std::move(wave));
683 }
684 else if (const XmlElement sfcw_element = waveform.childElement("stepped_frequency", 0); sfcw_element.isValid())
685 {
686 const RealType start_frequency_offset = get_child_real_type(sfcw_element, "start_frequency_offset");
687 const RealType step_size = get_child_real_type(sfcw_element, "step_size");
689 if (raw_step_count <= 0.0 || std::floor(raw_step_count) != raw_step_count)
690 {
691 throw XmlException("Waveform '" + name + "' has an invalid step_count.");
692 }
693 const RealType dwell_time = get_child_real_type(sfcw_element, "dwell_time");
694 const RealType step_period = get_child_real_type(sfcw_element, "step_period");
695
696 std::optional<std::size_t> sweep_count;
697 if (const auto sweep_count_element = sfcw_element.childElement("sweep_count", 0);
698 sweep_count_element.isValid())
699 {
700 const RealType raw_count = get_child_real_type(sfcw_element, "sweep_count");
701 if (raw_count <= 0.0 || std::floor(raw_count) != raw_count)
702 {
703 throw XmlException("Waveform '" + name + "' has an invalid sweep_count.");
704 }
705 sweep_count = static_cast<std::size_t>(raw_count);
706 }
707
708 auto sfcw_signal = std::make_unique<fers_signal::SteppedFrequencySignal>(
709 start_frequency_offset, step_size, static_cast<std::size_t>(raw_step_count), dwell_time, step_period,
710 sweep_count);
711 auto wave = std::make_unique<fers_signal::RadarSignal>(name, power, carrier, sfcw_signal->getDwellTime(),
712 std::move(sfcw_signal), id);
713 validate_fmcw_waveform(*wave, "Waveform '" + name + "'");
714 ctx.world->add(std::move(wave));
715 }
716 else if (const XmlElement fmcw_element = waveform.childElement("fmcw_linear_chirp", 0); fmcw_element.isValid())
717 {
718 const auto direction =
720 const RealType chirp_bandwidth = get_child_real_type(fmcw_element, "chirp_bandwidth");
721 const RealType chirp_duration = get_child_real_type(fmcw_element, "chirp_duration");
722 const RealType chirp_period = get_child_real_type(fmcw_element, "chirp_period");
723
724 RealType start_frequency_offset = 0.0;
725 if (const auto start_offset = fmcw_element.childElement("start_frequency_offset", 0);
726 start_offset.isValid())
727 {
728 start_frequency_offset = get_child_real_type(fmcw_element, "start_frequency_offset");
729 }
730
731 std::optional<std::size_t> chirp_count;
732 if (const auto chirp_count_element = fmcw_element.childElement("chirp_count", 0);
733 chirp_count_element.isValid())
734 {
735 const RealType raw_count = get_child_real_type(fmcw_element, "chirp_count");
736 if (raw_count <= 0.0 || std::floor(raw_count) != raw_count)
737 {
738 throw XmlException("Waveform '" + name + "' has an invalid chirp_count.");
739 }
740 chirp_count = static_cast<std::size_t>(raw_count);
741 }
742
743 auto fmcw_signal = std::make_unique<fers_signal::FmcwChirpSignal>(
744 chirp_bandwidth, chirp_duration, chirp_period, start_frequency_offset, chirp_count, direction);
745 // RadarSignal length is the active chirp duration, not T_rep. The repeat period only spaces chirps.
746 auto wave = std::make_unique<fers_signal::RadarSignal>(name, power, carrier, chirp_duration,
747 std::move(fmcw_signal), id);
748 validate_fmcw_waveform(*wave, "Waveform '" + name + "'");
749 ctx.world->add(std::move(wave));
750 }
751 else if (const XmlElement fmcw_triangle_element = waveform.childElement("fmcw_triangle", 0);
752 fmcw_triangle_element.isValid())
753 {
754 const RealType chirp_bandwidth = get_child_real_type(fmcw_triangle_element, "chirp_bandwidth");
755 const RealType chirp_duration = get_child_real_type(fmcw_triangle_element, "chirp_duration");
756
757 RealType start_frequency_offset = 0.0;
758 if (const auto start_offset = fmcw_triangle_element.childElement("start_frequency_offset", 0);
759 start_offset.isValid())
760 {
761 start_frequency_offset = get_child_real_type(fmcw_triangle_element, "start_frequency_offset");
762 }
763
764 std::optional<std::size_t> triangle_count;
765 if (const auto triangle_count_element = fmcw_triangle_element.childElement("triangle_count", 0);
766 triangle_count_element.isValid())
767 {
769 if (raw_count <= 0.0 || std::floor(raw_count) != raw_count)
770 {
771 throw XmlException("Waveform '" + name + "' has an invalid triangle_count.");
772 }
773 triangle_count = static_cast<std::size_t>(raw_count);
774 }
775
776 auto fmcw_signal = std::make_unique<fers_signal::FmcwTriangleSignal>(
777 chirp_bandwidth, chirp_duration, start_frequency_offset, triangle_count);
778 auto wave = std::make_unique<fers_signal::RadarSignal>(
779 name, power, carrier, fmcw_signal->getTrianglePeriod(), std::move(fmcw_signal), id);
780 validate_fmcw_waveform(*wave, "Waveform '" + name + "'");
781 ctx.world->add(std::move(wave));
782 }
783 else
784 {
785 LOG(logging::Level::FATAL, "Unsupported waveform type for '{}'", name);
786 throw XmlException("Unsupported waveform type for '" + name + "'");
787 }
788 }
FmcwChirpDirection parseFmcwChirpDirection(const std::string_view direction)
Parses a schema chirp direction token.

References assign_id_from_attribute(), XmlElement::childElement(), fers_signal::Cw, logging::FATAL, fers_signal::Fmcw, get_child_real_type(), XmlElement::getSafeAttribute(), XmlElement::isValid(), LOG, max, fers_signal::parseFmcwChirpDirection(), fers_signal::Pulsed, and Waveform.

Referenced by processParsedDocument().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ processParsedDocument()

void serial::xml_parser_utils::processParsedDocument ( const XmlDocument doc,
ParserContext ctx 
)

Coordinates the full parsing of a validated XML document tree.

This is the root parsing function that iterates over parameters, waveforms, timings, antennas, and platforms. It populates the World in the proper order and triggers initial event scheduling.

Parameters
docThe parsed XML document tree.
ctxThe parser context containing the World, isolated parameters, and asset loaders.

Definition at line 1460 of file xml_parser_utils.cpp.

1461 {
1462 const XmlElement root = doc.getRootElement();
1463 if (root.name() != "simulation")
1464 {
1465 throw XmlException("Root element is not <simulation>!");
1466 }
1467
1468 std::unordered_map<std::string, std::string> name_registry;
1469 name_registry.reserve(64); // TODO: reserve 64?
1470 const auto register_name = [&](const XmlElement& element, const std::string_view kind)
1471 {
1472 const std::string name = XmlElement::getSafeAttribute(element, "name");
1473 const auto [iter, inserted] = name_registry.emplace(name, std::string(kind));
1474 if (!inserted)
1475 {
1476 throw XmlException("Duplicate name '" + name + "' found for " + std::string(kind) +
1477 "; previously used by " + iter->second + ".");
1478 }
1479 };
1480
1481 try
1482 {
1483 ctx.parameters.simulation_name = XmlElement::getSafeAttribute(root, "name");
1484 if (!ctx.parameters.simulation_name.empty())
1485 {
1486 LOG(logging::Level::INFO, "Simulation name set to: {}", ctx.parameters.simulation_name);
1487 }
1488 }
1489 catch (const XmlException&)
1490 {
1491 LOG(logging::Level::WARNING, "No 'name' attribute found in <simulation> tag. KML name will default.");
1492 }
1493
1494 parseParameters(root.childElement("parameters", 0), ctx.parameters);
1495
1496 params::params = ctx.parameters;
1497
1498 auto parseElements =
1499 [](const XmlElement& parent, const std::string& elementName, ParserContext& parser_ctx, auto parseFunction)
1500 {
1501 unsigned index = 0;
1502 while (true)
1503 {
1504 XmlElement const element = parent.childElement(elementName, index++);
1505 if (!element.isValid())
1506 break;
1508 }
1509 };
1510
1511 parseElements(root, "waveform", ctx,
1512 [&](const XmlElement& p, ParserContext& c)
1513 {
1514 register_name(p, "waveform");
1515 parseWaveform(p, c);
1516 });
1517
1518 parseElements(root, "timing", ctx,
1519 [&](const XmlElement& p, ParserContext& c)
1520 {
1521 register_name(p, "timing");
1522 parseTiming(p, c);
1523 });
1524
1525 parseElements(root, "antenna", ctx,
1526 [&](const XmlElement& p, ParserContext& c)
1527 {
1528 register_name(p, "antenna");
1529 parseAntenna(p, c);
1530 });
1531
1532 std::unordered_map<std::string, SimId> waveform_refs;
1533 std::unordered_map<std::string, SimId> antenna_refs;
1534 std::unordered_map<std::string, SimId> timing_refs;
1535 waveform_refs.reserve(ctx.world->getWaveforms().size());
1536 antenna_refs.reserve(ctx.world->getAntennas().size());
1537 timing_refs.reserve(ctx.world->getTimings().size());
1538
1539 for (const auto& [id, waveform] : ctx.world->getWaveforms())
1540 waveform_refs.emplace(waveform->getName(), id);
1541 for (const auto& [id, antenna] : ctx.world->getAntennas())
1542 antenna_refs.emplace(antenna->getName(), id);
1543 for (const auto& [id, timing] : ctx.world->getTimings())
1544 timing_refs.emplace(timing->getName(), id);
1545
1546 const ReferenceLookup refs{&waveform_refs, &antenna_refs, &timing_refs};
1547
1548 parseElements(root, "platform", ctx,
1549 [&](const XmlElement& p, ParserContext& c)
1550 {
1551 register_name(p, "platform");
1553 });
1554
1555 ctx.world->resolveReceiverDechirpReferences();
1556
1557 ctx.world->scheduleInitialEvents();
1558
1559 LOG(logging::Level::DEBUG, "Initial Event Queue State:\n{}", ctx.world->dumpEventQueue());
1560 }
Parameters params
Global simulation parameter state.
Definition parameters.h:85
void parseAntenna(const XmlElement &antenna, ParserContext &ctx)
Parses an <antenna> block and adds it to the World.
void parseWaveform(const XmlElement &waveform, ParserContext &ctx)
Parses a <waveform> block and adds it to the World.
void parseTiming(const XmlElement &timing, ParserContext &ctx)
Parses a <timing> block and adds the prototype timing to the World.
void parseParameters(const XmlElement &parameters, params::Parameters &params_out)
Parses the <parameters> block into the isolated context parameters.
void parsePlatform(const XmlElement &platform, ParserContext &ctx, const std::function< void(const XmlElement &, std::string_view)> &register_name, const ReferenceLookup &refs)
Parses a complete <platform> block, including its motion paths and sub-elements.
RealType c

References c, logging::DEBUG, XmlElement::getSafeAttribute(), logging::INFO, LOG, max, params::params, parseAntenna(), parseParameters(), parsePlatform(), parseTiming(), parseWaveform(), and logging::WARNING.

Referenced by serial::parseSimulation(), and serial::parseSimulationFromString().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ resolve_reference_id()

SimId serial::xml_parser_utils::resolve_reference_id ( const XmlElement element,
const std::string &  attributeName,
const std::string &  owner,
const std::unordered_map< std::string, SimId > &  name_map 
)

Resolves an XML string reference into an internal SimId.

Parameters
elementThe XML element containing the string reference attribute.
attributeNameThe name of the attribute containing the reference string.
ownerA description of the object making the reference (used for error messages).
name_mapThe lookup table mapping string names to SimIds.
Returns
The resolved SimId.
Exceptions
XmlExceptionif the reference cannot be resolved or is missing.

Definition at line 367 of file xml_parser_utils.cpp.

369 {
370 const std::string value = XmlElement::getSafeAttribute(element, attributeName);
371 if (value.empty())
372 {
373 throw XmlException("Missing " + attributeName + " for " + owner + ".");
374 }
375 const auto it = name_map.find(value);
376 if (it != name_map.end())
377 {
378 return it->second;
379 }
380 throw XmlException("Unknown " + attributeName + " '" + value + "' for " + owner + ".");
381 }

References XmlElement::getSafeAttribute(), and max.

Referenced by parseReceiverWithMode(), and parseTransmitterWithMode().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ setOptionalRealParameter()

template<typename Setter >
void serial::xml_parser_utils::setOptionalRealParameter ( const XmlElement parameters,
const std::string &  param_name,
const RealType  default_value,
Setter  setter 
)

Definition at line 433 of file xml_parser_utils.cpp.

435 {
436 if (!parameters.childElement(param_name, 0).isValid())
437 {
438 LOG(logging::Level::DEBUG, "Parameter '{}' not specified. Using default value {}.", param_name,
440 return;
441 }
442
443 setter(get_child_real_type(parameters, param_name));
444 }

References XmlElement::childElement(), logging::DEBUG, get_child_real_type(), XmlElement::isValid(), LOG, and max.

Referenced by parseOptionalNumericParameters().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ setOptionalUnsignedParameter()

template<typename Setter >
void serial::xml_parser_utils::setOptionalUnsignedParameter ( const XmlElement parameters,
const std::string &  param_name,
const unsigned  default_value,
Setter  setter 
)

Definition at line 447 of file xml_parser_utils.cpp.

449 {
450 if (!parameters.childElement(param_name, 0).isValid())
451 {
452 LOG(logging::Level::DEBUG, "Parameter '{}' not specified. Using default value {}.", param_name,
454 return;
455 }
456
457 setter(parseUnsignedParameter(param_name, get_child_real_type(parameters, param_name)));
458 }

References XmlElement::childElement(), logging::DEBUG, get_child_real_type(), XmlElement::isValid(), LOG, max, and parseUnsignedParameter().

Referenced by parseOptionalNumericParameters().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ validateXml()

void serial::xml_parser_utils::validateXml ( bool  didCombine,
const XmlDocument mainDoc 
)

Validates an XML document against the embedded DTD and XSD schemas.

Parameters
didCombineFlag indicating whether the document contains merged includes (used for formatting log messages).
mainDocThe XML document to validate.
Exceptions
XmlExceptionif validation fails.

Definition at line 1442 of file xml_parser_utils.cpp.

1443 {
1444 LOG(logging::Level::DEBUG, "Validating the{}XML file...", didCombine ? " combined " : " ");
1445 if (!mainDoc.validateWithDtd(fers_xml_dtd))
1446 {
1447 LOG(logging::Level::FATAL, "{} XML file failed DTD validation!", didCombine ? "Combined" : "Main");
1448 throw XmlException("XML file failed DTD validation!");
1449 }
1450 LOG(logging::Level::DEBUG, "{} XML file passed DTD validation.", didCombine ? "Combined" : "Main");
1451
1452 if (!mainDoc.validateWithXsd(fers_xml_xsd))
1453 {
1454 LOG(logging::Level::FATAL, "{} XML file failed XSD validation!", didCombine ? "Combined" : "Main");
1455 throw XmlException("XML file failed XSD validation!");
1456 }
1457 LOG(logging::Level::DEBUG, "{} XML file passed XSD validation.", didCombine ? "Combined" : "Main");
1458 }

References logging::DEBUG, logging::FATAL, LOG, and max.

Referenced by serial::parseSimulation(), and serial::parseSimulationFromString().

+ Here is the caller graph for this function: