diff --git a/format_schema.py b/format_schema.py index b7ae61978..a4fcaefc0 100644 --- a/format_schema.py +++ b/format_schema.py @@ -6,23 +6,19 @@ for line in lines: # Remove .sdf extension snake_case = line.strip().split('.')[0] - # Convert from snake_case to CamelCase camel_case = ''.join([word.capitalize() for word in snake_case.split('_')]) - addHeader = """ - /// \\brief Get the schema file name accessor - public: static inline std::string_view SchemaFile(); - """ + addHeader = """\n /// \\brief Get the schema file name accessor + public: static inline std::string_view SchemaFile();\n""" addImpl = f""" ///////////////////////////////////////////////// -inline std::string_view {camel_case}::SchemaFile() +inline std::string_view {camel_case}::SchemaFile() {{ static const char kSchemaFile[] = "{line}"; return kSchemaFile; }}\n\n""" - # Debug print statements # print(addHeader) # print(addImpl) @@ -31,31 +27,31 @@ print(line) - # # Edit './include/sdf/{camel_case}.hh' - # # Find the line with 'class SDFFORMAT_VISIBLE {camel_case}` - # try: - # f = open('./include/sdf/' + camel_case + '.hh', 'r') - # read_lines = f.readlines() - # f.close() - # line_number = 0 - # for i, line_raw in enumerate(read_lines): - # if line_raw == " class SDFORMAT_VISIBLE " + camel_case + '\n': - # line_number = i+3 # After the class line there is `\n{\n` and then the constructor - # break - # if line_number == 0: - # print("Error: Could not find class declaration in " + camel_case + ".hh") - # exit(1) - # try: - # with open('./include/sdf/' + camel_case + '.hh', 'w') as file: - # for i, line_raw in enumerate(read_lines): - # file.write(line_raw) - # if i == line_number: - # file.write(addHeader) - # except: - # print("Unexpected error while writing to: " + camel_case + ".hh.") - # except: - # print("Error while writing to: " + camel_case + ".hh." + " Check if file exists.") - + # Edit './include/sdf/{camel_case}.hh' + # Find the line with 'class SDFFORMAT_VISIBLE {camel_case}` + try: + f = open('./include/sdf/' + camel_case + '.hh', 'r') + read_lines = f.readlines() + f.close() + line_number = 0 + for i, line_raw in enumerate(read_lines): + if line_raw == " class SDFORMAT_VISIBLE " + camel_case + '\n': + line_number = i+3 # After the class line there is `\n{\n` and then the constructor + break + if line_number == 0: + print("Error: Could not find class declaration in " + camel_case + ".hh") + exit(1) + try: + with open('./include/sdf/' + camel_case + '.hh', 'w') as file: + for i, line_raw in enumerate(read_lines): + file.write(line_raw) + if i == line_number: + file.write(addHeader) + except: + print("Unexpected error while writing to: " + camel_case + ".hh.") + except: + print("Error while writing to: " + camel_case + ".hh." + " Check if file exists.") + # Replace all instances of `line` with `std::string(this->SchemaFile())` if (os.path.exists('./src/' + camel_case + '.cc')): try: @@ -72,12 +68,12 @@ except: print("Unexpected error while reading from: " + camel_case + ".cc.") - # # Edit './src/{camel_case}.cc' if it exists - # # Add implementation to end of document - # if os.path.exists('./src/' + camel_case + '.cc'): - # with open('./src/' + camel_case + '.cc', 'a') as file: - # file.write(addImpl) - # else: - # print("Error: Could not find " + camel_case + ".cc") + # Edit './src/{camel_case}.cc' if it exists + # Add implementation to end of document + if os.path.exists('./src/' + camel_case + '.cc'): + with open('./src/' + camel_case + '.cc', 'a') as file: + file.write(addImpl) + else: + print("Error: Could not find " + camel_case + ".cc") print("Changes written to source successfully") diff --git a/include/sdf/Actor.hh b/include/sdf/Actor.hh index 57c5c0fc9..7708f8559 100644 --- a/include/sdf/Actor.hh +++ b/include/sdf/Actor.hh @@ -195,7 +195,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the actor based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/AirPressure.hh b/include/sdf/AirPressure.hh index b8fae845e..2b3ae5491 100644 --- a/include/sdf/AirPressure.hh +++ b/include/sdf/AirPressure.hh @@ -38,7 +38,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the airPressure based on an element pointer. /// This is *not* the usual entry point. Typical usage of the SDF DOM is /// through the Root object. diff --git a/include/sdf/AirSpeed.hh b/include/sdf/AirSpeed.hh index 050c7fac6..2ad60b994 100644 --- a/include/sdf/AirSpeed.hh +++ b/include/sdf/AirSpeed.hh @@ -38,7 +38,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the air speed based on an element pointer. /// This is *not* the usual entry point. Typical usage of the SDF DOM is /// through the Root object. diff --git a/include/sdf/Altimeter.hh b/include/sdf/Altimeter.hh index d6ac7a962..df769fc31 100644 --- a/include/sdf/Altimeter.hh +++ b/include/sdf/Altimeter.hh @@ -37,7 +37,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the altimeter based on an element pointer. This is *not* /// the usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Atmosphere.hh b/include/sdf/Atmosphere.hh index 3efb86813..825544df0 100644 --- a/include/sdf/Atmosphere.hh +++ b/include/sdf/Atmosphere.hh @@ -49,7 +49,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the atmosphere based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Camera.hh b/include/sdf/Camera.hh index 6b25af210..8c7ce146a 100644 --- a/include/sdf/Camera.hh +++ b/include/sdf/Camera.hh @@ -64,7 +64,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Return true if both Camera objects contain the same values. /// \param[_in] _alt Camera value to compare. /// \returen True if 'this' == _alt. diff --git a/include/sdf/Collision.hh b/include/sdf/Collision.hh index f49700f59..ef693d405 100644 --- a/include/sdf/Collision.hh +++ b/include/sdf/Collision.hh @@ -54,7 +54,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the collision based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Error.hh b/include/sdf/Error.hh index 0aace5ef7..9664cc850 100644 --- a/include/sdf/Error.hh +++ b/include/sdf/Error.hh @@ -183,6 +183,9 @@ namespace sdf /// \brief The joint axis mimic does not refer to a valid joint in the /// current scope. JOINT_AXIS_MIMIC_INVALID, + + /// \brief Error at the XML level. + XML_ERROR, }; class SDFORMAT_VISIBLE Error diff --git a/include/sdf/ForceTorque.hh b/include/sdf/ForceTorque.hh index c84cd693f..04fb09a86 100644 --- a/include/sdf/ForceTorque.hh +++ b/include/sdf/ForceTorque.hh @@ -66,7 +66,8 @@ namespace sdf /// \brief Default constructor public: ForceTorque(); - public: static inline std::string_view SchemaFile(); + /// \brief Get the schema file name accessor + public: static inline std::string_view SchemaFile(); /// \brief Load the force torque sensor based on an element pointer. This is /// *not* the usual entry point. Typical usage of the SDF DOM is through the diff --git a/include/sdf/Frame.hh b/include/sdf/Frame.hh index 90e9d0d45..d57988c15 100644 --- a/include/sdf/Frame.hh +++ b/include/sdf/Frame.hh @@ -46,7 +46,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the frame based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Geometry.hh b/include/sdf/Geometry.hh index 5f5443ec0..641f4c2eb 100644 --- a/include/sdf/Geometry.hh +++ b/include/sdf/Geometry.hh @@ -96,7 +96,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the geometry based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Gui.hh b/include/sdf/Gui.hh index a66ab4d41..00b4b2432 100644 --- a/include/sdf/Gui.hh +++ b/include/sdf/Gui.hh @@ -35,7 +35,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the gui based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Imu.hh b/include/sdf/Imu.hh index 83ce17a40..7c3cbe26a 100644 --- a/include/sdf/Imu.hh +++ b/include/sdf/Imu.hh @@ -37,7 +37,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the IMU based on an element pointer. This is *not* /// the usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Joint.hh b/include/sdf/Joint.hh index 1147ad4e2..dc908cdb9 100644 --- a/include/sdf/Joint.hh +++ b/include/sdf/Joint.hh @@ -89,7 +89,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the joint based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Lidar.hh b/include/sdf/Lidar.hh index dac6bb4ab..d72929e8d 100644 --- a/include/sdf/Lidar.hh +++ b/include/sdf/Lidar.hh @@ -109,7 +109,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the lidar based on an element pointer. This is *not* /// the usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Light.hh b/include/sdf/Light.hh index 21f31e763..e2bf64725 100644 --- a/include/sdf/Light.hh +++ b/include/sdf/Light.hh @@ -67,7 +67,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the light based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Link.hh b/include/sdf/Link.hh index 291fa27b0..5da46e7d6 100644 --- a/include/sdf/Link.hh +++ b/include/sdf/Link.hh @@ -55,7 +55,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the link based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Magnetometer.hh b/include/sdf/Magnetometer.hh index db1d4f697..07dcbe08a 100644 --- a/include/sdf/Magnetometer.hh +++ b/include/sdf/Magnetometer.hh @@ -38,7 +38,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the magnetometer based on an element pointer. This is *not* /// the usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Material.hh b/include/sdf/Material.hh index 32115d9b6..5269f9819 100644 --- a/include/sdf/Material.hh +++ b/include/sdf/Material.hh @@ -50,7 +50,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the material based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Model.hh b/include/sdf/Model.hh index 163121965..8f39b0e29 100644 --- a/include/sdf/Model.hh +++ b/include/sdf/Model.hh @@ -58,7 +58,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the model based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/NavSat.hh b/include/sdf/NavSat.hh index e85b6d0f6..5a0e23367 100644 --- a/include/sdf/NavSat.hh +++ b/include/sdf/NavSat.hh @@ -76,15 +76,16 @@ namespace sdf { /// \brief Default constructor public: NavSat(); + + /// \brief Get the schema file name accessor + public: static inline std::string_view SchemaFile(); + /// \brief Load the navsat based on an element pointer. This is *not* /// the usual entry point. Typical usage of the SDF DOM is through the Root /// object. /// \param[in] _sdf The SDF Element pointer /// \return Errors, which is a vector of Error objects. Each Error includes /// an error code and message. An empty vector indicates no error. - - public: static inline std::string_view SchemaFile(); - public: Errors Load(ElementPtr _sdf); /// \brief Get a pointer to the SDF element that was used during diff --git a/include/sdf/Noise.hh b/include/sdf/Noise.hh index 202f57efb..aad165615 100644 --- a/include/sdf/Noise.hh +++ b/include/sdf/Noise.hh @@ -51,7 +51,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Return true if both Noise objects contain the same values. /// \param[_in] _noise Noise value to compare. /// \return True if 'this' == _noise. diff --git a/include/sdf/ParticleEmitter.hh b/include/sdf/ParticleEmitter.hh index d20534db1..dc2f4d98d 100644 --- a/include/sdf/ParticleEmitter.hh +++ b/include/sdf/ParticleEmitter.hh @@ -64,7 +64,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the particle emitter based on an element pointer. This is /// *not* the usual entry point. Typical usage of the SDF DOM is through /// the Root object. diff --git a/include/sdf/Physics.hh b/include/sdf/Physics.hh index 6bd63e54b..0a93a8802 100644 --- a/include/sdf/Physics.hh +++ b/include/sdf/Physics.hh @@ -40,7 +40,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the physics based on an element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Plugin.hh b/include/sdf/Plugin.hh index bc49a4f0d..b530bc8a5 100644 --- a/include/sdf/Plugin.hh +++ b/include/sdf/Plugin.hh @@ -49,7 +49,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Default destructor public: ~Plugin(); diff --git a/include/sdf/Projector.hh b/include/sdf/Projector.hh index a4c8bdd81..322489d6e 100644 --- a/include/sdf/Projector.hh +++ b/include/sdf/Projector.hh @@ -47,7 +47,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the projector based on an element pointer. This is /// *not* the usual entry point. Typical usage of the SDF DOM is through /// the Root object. diff --git a/include/sdf/Root.hh b/include/sdf/Root.hh index a8c823cf2..9f10c05ae 100644 --- a/include/sdf/Root.hh +++ b/include/sdf/Root.hh @@ -61,7 +61,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Get the name of the world without loading the entire world /// Users shouldn't normally need to use this API. /// This doesn't load the world, it might return the world name even if the diff --git a/include/sdf/SDFImpl.hh b/include/sdf/SDFImpl.hh index 20e1ec19f..277d45188 100644 --- a/include/sdf/SDFImpl.hh +++ b/include/sdf/SDFImpl.hh @@ -80,6 +80,34 @@ namespace sdf bool _searchLocalPath = true, bool _useCallback = false); + /// \brief Find the absolute path of a file. + /// + /// The search order in the function is as follows: + /// 1. Using the global URI path map, search in paths associated with the URI + /// scheme of the input. + /// 2. Seach in the path defined by the macro `SDF_SHARE_PATH`. + /// 3. Search in the the libsdformat install path. The path is formed by + /// has the pattern `SDF_SHARE_PATH/sdformat//` + /// 4. Directly check if the input path exists in the filesystem. + /// 5. Seach in the path defined by the environment variable `SDF_PATH`. + /// 6. If enabled via _searchLocalPath, prepend the input with the current + /// working directory and check if the result path exists. + /// 7. If enabled via _useCallback and the global callback function is set, + /// invoke the function and return its result. + /// + /// \param[out] _errors Vector of errors. + /// \param[in] _filename Name of the file to find. + /// \param[in] _searchLocalPath True to search for the file in the current + /// working directory. + /// \param[in] _useCallback True to find a file based on a registered + /// callback if the file is not found via the normal mechanism. + /// \return File's full path. + SDFORMAT_VISIBLE + std::string findFile(sdf::Errors &_errors, + const std::string &_filename, + bool _searchLocalPath = true, + bool _useCallback = false); + /// \brief Find the absolute path of a file. /// /// This overload uses the URI path map and and the callback function @@ -99,6 +127,26 @@ namespace sdf bool _useCallback, const ParserConfig &_config); + /// \brief Find the absolute path of a file. + /// + /// This overload uses the URI path map and and the callback function + /// configured in the input ParserConfig object instead of their global + /// counterparts. + /// + /// \param[out] _errors Vector of errors. + /// \param[in] _filename Name of the file to find. + /// \param[in] _searchLocalPath True to search for the file in the current + /// working directory. + /// \param[in] _useCallback True to find a file based on a registered + /// callback if the file is not found via the normal mechanism. + /// \param[in] _config Custom parser configuration. + /// \return File's full path. + SDFORMAT_VISIBLE + std::string findFile(sdf::Errors &_errors, + const std::string &_filename, + bool _searchLocalPath, + bool _useCallback, + const ParserConfig &_config); /// \brief Associate paths to a URI. /// Example paramters: "model://", "/usr/share/models:~/.gazebo/models" @@ -121,22 +169,45 @@ namespace sdf /// \brief Destructor public: ~SDF(); public: void PrintDescription(); + public: void PrintDescription(sdf::Errors &_errors); public: void PrintDoc(); public: void Write(const std::string &_filename); + public: void Write(sdf::Errors &_errors, const std::string &_filename); /// \brief Output SDF's values to stdout. /// \param[in] _config Configuration for printing the values. public: void PrintValues(const PrintConfig &_config = PrintConfig()); + /// \brief Output SDF's values to stdout. + /// \param[out] _errors Vector of errrors. + /// \param[in] _config Configuration for printing the values. + public: void PrintValues(sdf::Errors &_errors, + const PrintConfig &_config = PrintConfig()); + + /// \brief Convert the SDF values to a string representation. + /// \param[in] _config Configuration for printing the values. + /// \return The string representation. + public: std::string ToString( + const PrintConfig &_config = PrintConfig()) const; + /// \brief Convert the SDF values to a string representation. + /// \param[out] _errors Vector of errors. /// \param[in] _config Configuration for printing the values. /// \return The string representation. public: std::string ToString( + sdf::Errors &_errors, const PrintConfig &_config = PrintConfig()) const; /// \brief Set SDF values from a string + /// \param[in] sdfData String with the values to load. public: void SetFromString(const std::string &_sdfData); + /// \brief Set SDF values from a string + /// \param[out] _errors Vector of errors. + /// \param[in] sdfData String with the values to load. + public: void SetFromString(sdf::Errors &_Errors, + const std::string &_sdfData); + /// \brief Clear the data in this object. public: void Clear(); @@ -182,6 +253,13 @@ namespace sdf /// \return a wrapped clone of the SDF element public: static ElementPtr WrapInRoot(const ElementPtr &_sdf); + /// \brief wraps the SDF element into a root element with the version info. + /// \param[out] _errors Vector of errors. + /// \param[in] _sdf the sdf element. Will be cloned by this function. + /// \return a wrapped clone of the SDF element + public: static ElementPtr WrapInRoot(sdf::Errors &_errors, + const ElementPtr &_sdf); + /// \brief Get a string representation of an SDF specification file. /// This function uses a built-in version of a .sdf file located in /// the sdf directory. The parser.cc code uses this function, which avoids @@ -198,6 +276,22 @@ namespace sdf public: static const std::string &EmbeddedSpec( const std::string &_filename, const bool _quiet); + /// \brief Get a string representation of an SDF specification file. + /// This function uses a built-in version of a .sdf file located in + /// the sdf directory. The parser.cc code uses this function, which avoids + /// touching the filesystem. + /// + /// Most people should not use this function. + /// + /// \param[out] _errors Vector of errors. + /// \param[in] _filename Base name of the SDF specification file to + /// load. For example "root.sdf" or "world.sdf". + /// \return A string that contains the contents of the specified + /// _filename. An empty string is returned if the _filename could not be + /// found. + public: static const std::string &EmbeddedSpec( + sdf::Errors &_errors, const std::string &_filename); + /// \internal /// \brief Pointer to private data. private: std::unique_ptr dataPtr; diff --git a/include/sdf/Scene.hh b/include/sdf/Scene.hh index 5c33334c3..726d70b7e 100644 --- a/include/sdf/Scene.hh +++ b/include/sdf/Scene.hh @@ -37,7 +37,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the scene based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Sensor.hh b/include/sdf/Sensor.hh index 38c3f1fce..36cea8495 100644 --- a/include/sdf/Sensor.hh +++ b/include/sdf/Sensor.hh @@ -144,7 +144,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the sensor based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Surface.hh b/include/sdf/Surface.hh index 09590aef6..cf2ff772a 100644 --- a/include/sdf/Surface.hh +++ b/include/sdf/Surface.hh @@ -308,7 +308,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the surface based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/Visual.hh b/include/sdf/Visual.hh index 0f5808851..d1c860bea 100644 --- a/include/sdf/Visual.hh +++ b/include/sdf/Visual.hh @@ -53,7 +53,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the visual based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. diff --git a/include/sdf/World.hh b/include/sdf/World.hh index 4d68a3dcc..e1c5123bd 100644 --- a/include/sdf/World.hh +++ b/include/sdf/World.hh @@ -62,7 +62,7 @@ namespace sdf /// \brief Get the schema file name accessor public: static inline std::string_view SchemaFile(); - + /// \brief Load the world based on a element pointer. This is *not* the /// usual entry point. Typical usage of the SDF DOM is through the Root /// object. @@ -281,6 +281,20 @@ namespace sdf /// \return True if there exists an actor with the given name. public: bool ActorNameExists(const std::string &_name) const; + /// \brief Get an actor based on a name. + /// \param[in] _name Name of the actor. + /// \return Pointer to the actor. Nullptr if an actor with the given name + /// does not exist. + /// \sa bool ActorNameExists(const std::string &_name) const + public: const Actor *ActorByName(const std::string &_name) const; + + /// \brief Get a mutable actor based on a name. + /// \param[in] _name Name of the actor. + /// \return Pointer to the actor. Nullptr if an actor with the given name + /// does not exist. + /// \sa bool ActorNameExists(const std::string &_name) const + public: Actor *ActorByName(const std::string &_name); + /// \brief Get the number of explicit frames that are immediate (not nested) /// children of this World object. /// \remark FrameByName() can find explicit frames that are not immediate diff --git a/src/Actor.cc b/src/Actor.cc index 0ca834536..5efaf9c98 100644 --- a/src/Actor.cc +++ b/src/Actor.cc @@ -821,9 +821,9 @@ void Actor::AddPlugin(const Plugin &_plugin) } ///////////////////////////////////////////////// -inline std::string_view Actor::SchemaFile() +inline std::string_view Actor::SchemaFile() { - static char kSchemaFile[] = "actor.sdf"; + static const char kSchemaFile[] = "actor.sdf"; return kSchemaFile; } diff --git a/src/AirPressure.cc b/src/AirPressure.cc index 1f576ed59..f35e841a3 100644 --- a/src/AirPressure.cc +++ b/src/AirPressure.cc @@ -141,9 +141,9 @@ sdf::ElementPtr AirPressure::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view AirPressure::SchemaFile() +inline std::string_view AirPressure::SchemaFile() { - static char kSchemaFile[] = "air_pressure.sdf"; + static const char kSchemaFile[] = "air_pressure.sdf"; return kSchemaFile; } diff --git a/src/AirSpeed.cc b/src/AirSpeed.cc index fd0dec211..0a9e77945 100644 --- a/src/AirSpeed.cc +++ b/src/AirSpeed.cc @@ -108,9 +108,9 @@ sdf::ElementPtr AirSpeed::ToElement() const } ///////////////////////////////////////////////// -inline std::string_view AirSpeed::SchemaFile() +inline std::string_view AirSpeed::SchemaFile() { - static char kSchemaFile[] = "air_speed.sdf"; + static const char kSchemaFile[] = "air_speed.sdf"; return kSchemaFile; } diff --git a/src/Altimeter.cc b/src/Altimeter.cc index f7556c485..d4f421fcb 100644 --- a/src/Altimeter.cc +++ b/src/Altimeter.cc @@ -171,9 +171,9 @@ sdf::ElementPtr Altimeter::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Altimeter::SchemaFile() +inline std::string_view Altimeter::SchemaFile() { - static char kSchemaFile[] = "altimeter.sdf"; + static const char kSchemaFile[] = "altimeter.sdf"; return kSchemaFile; } diff --git a/src/Atmosphere.cc b/src/Atmosphere.cc index 1b4459430..286b55d96 100644 --- a/src/Atmosphere.cc +++ b/src/Atmosphere.cc @@ -174,9 +174,9 @@ sdf::ElementPtr Atmosphere::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Atmosphere::SchemaFile() +inline std::string_view Atmosphere::SchemaFile() { - static char kSchemaFile[] = "atmosphere.sdf"; + static const char kSchemaFile[] = "atmosphere.sdf"; return kSchemaFile; } diff --git a/src/Camera.cc b/src/Camera.cc index 884ce36e1..a18b53ea8 100644 --- a/src/Camera.cc +++ b/src/Camera.cc @@ -1339,9 +1339,9 @@ sdf::ElementPtr Camera::ToElement() const } ///////////////////////////////////////////////// -inline std::string_view Camera::SchemaFile() +inline std::string_view Camera::SchemaFile() { - static char kSchemaFile[] = "camera.sdf"; + static const char kSchemaFile[] = "camera.sdf"; return kSchemaFile; } diff --git a/src/Collision.cc b/src/Collision.cc index 5a3fde531..d668a57f0 100644 --- a/src/Collision.cc +++ b/src/Collision.cc @@ -394,9 +394,9 @@ sdf::ElementPtr Collision::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Collision::SchemaFile() +inline std::string_view Collision::SchemaFile() { - static char kSchemaFile[] = "collision.sdf"; + static const char kSchemaFile[] = "collision.sdf"; return kSchemaFile; } diff --git a/src/Converter.cc b/src/Converter.cc index 8947d3333..41d550df1 100644 --- a/src/Converter.cc +++ b/src/Converter.cc @@ -264,7 +264,7 @@ void Converter::ConvertImpl(tinyxml2::XMLElement *_elem, } else if (name == "copy") { - Move(_elem, childElem, true); + Move(_elem, childElem, true, _errors); } else if (name == "map") { @@ -272,7 +272,7 @@ void Converter::ConvertImpl(tinyxml2::XMLElement *_elem, } else if (name == "move") { - Move(_elem, childElem, false); + Move(_elem, childElem, false, _errors); } else if (name == "add") { @@ -923,7 +923,8 @@ void Converter::Map(tinyxml2::XMLElement *_elem, ///////////////////////////////////////////////// void Converter::Move(tinyxml2::XMLElement *_elem, tinyxml2::XMLElement *_moveElem, - const bool _copy) + const bool _copy, + sdf::Errors &_errors) { SDF_ASSERT(_elem != NULL, "SDF element is NULL"); SDF_ASSERT(_moveElem != NULL, "Move element is NULL"); @@ -1024,7 +1025,8 @@ void Converter::Move(tinyxml2::XMLElement *_elem, if (toElemStr && !toAttrStr) { - tinyxml2::XMLNode *cloned = DeepClone(moveFrom->GetDocument(), moveFrom); + tinyxml2::XMLNode *cloned = DeepClone(_errors, moveFrom->GetDocument(), + moveFrom); tinyxml2::XMLElement *moveTo = static_cast(cloned); moveTo->SetValue(toName); diff --git a/src/Converter.hh b/src/Converter.hh index e992b124c..cfba5413f 100644 --- a/src/Converter.hh +++ b/src/Converter.hh @@ -108,9 +108,11 @@ namespace sdf /// \param[in] _moveElem A 'convert' element that describes the move /// operation. /// \param[in] _copy True to copy the element + /// \param[out] _errors Vector of errors. private: static void Move(tinyxml2::XMLElement *_elem, tinyxml2::XMLElement *_moveElem, - const bool _copy); + const bool _copy, + sdf::Errors &_errors); /// \brief Add an element or attribute to an element. /// \param[in] _elem The element to receive the value. diff --git a/src/Converter_TEST.cc b/src/Converter_TEST.cc index 443774017..d56cba30a 100644 --- a/src/Converter_TEST.cc +++ b/src/Converter_TEST.cc @@ -3370,7 +3370,8 @@ TEST(Converter, World_17_to_18) ASSERT_TRUE(errors.empty()); // Compare converted xml with expected - std::string convertedXmlStr = ElementToString(xmlDoc.RootElement()); + std::string convertedXmlStr = ElementToString(errors, xmlDoc.RootElement()); + ASSERT_TRUE(errors.empty()); ASSERT_FALSE(convertedXmlStr.empty()); std::string expectedXmlStr = R"( @@ -3393,7 +3394,9 @@ TEST(Converter, World_17_to_18) tinyxml2::XMLDocument expectedXmlDoc; expectedXmlDoc.Parse(expectedXmlStr.c_str()); - EXPECT_EQ(ElementToString(expectedXmlDoc.RootElement()), convertedXmlStr); + EXPECT_EQ(ElementToString(errors, expectedXmlDoc.RootElement()), + convertedXmlStr); + ASSERT_TRUE(errors.empty()); // Check some basic elements tinyxml2::XMLElement *convertedElem = xmlDoc.FirstChildElement(); @@ -3509,7 +3512,8 @@ TEST(Converter, World_17_to_18) ASSERT_TRUE(errors.empty()); // Compare converted xml with expected - convertedXmlStr = ElementToString(xmlDoc.RootElement()); + convertedXmlStr = ElementToString(errors, xmlDoc.RootElement()); + ASSERT_TRUE(errors.empty()); ASSERT_FALSE(convertedXmlStr.empty()); expectedXmlStr = R"( @@ -3577,7 +3581,9 @@ TEST(Converter, World_17_to_18) expectedXmlDoc.Clear(); expectedXmlDoc.Parse(expectedXmlStr.c_str()); - EXPECT_EQ(ElementToString(expectedXmlDoc.RootElement()), convertedXmlStr); + EXPECT_EQ(ElementToString(errors, expectedXmlDoc.RootElement()), + convertedXmlStr); + ASSERT_TRUE(errors.empty()); // ------- Another flattened world in 1.7 format @@ -3616,7 +3622,8 @@ TEST(Converter, World_17_to_18) EXPECT_TRUE(buffer.str().empty()) << buffer.str(); // Compare converted xml with expected - convertedXmlStr = ElementToString(xmlDoc.RootElement()); + convertedXmlStr = ElementToString(errors, xmlDoc.RootElement()); + ASSERT_TRUE(errors.empty()); ASSERT_FALSE(convertedXmlStr.empty()); expectedXmlStr = R"( @@ -3655,7 +3662,9 @@ TEST(Converter, World_17_to_18) expectedXmlDoc.Clear(); expectedXmlDoc.Parse(expectedXmlStr.c_str()); - EXPECT_EQ(ElementToString(expectedXmlDoc.RootElement()), convertedXmlStr); + EXPECT_EQ(ElementToString(errors, expectedXmlDoc.RootElement()), + convertedXmlStr); + ASSERT_TRUE(errors.empty()); // Check some basic elements convertedElem = xmlDoc.FirstChildElement(); diff --git a/src/ForceTorque.cc b/src/ForceTorque.cc index e55f56c06..9b6ba0bc7 100644 --- a/src/ForceTorque.cc +++ b/src/ForceTorque.cc @@ -369,9 +369,9 @@ sdf::ElementPtr ForceTorque::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view ForceTorque::SchemaFile() +inline std::string_view ForceTorque::SchemaFile() { - static char kSchemaFile[] = "forcetorque.sdf"; - return kSchemaFile; + static const char kSchemaFile[] = "forcetorque.sdf"; + return kSchemaFile; } diff --git a/src/Frame.cc b/src/Frame.cc index bf5f4d243..5410f741c 100644 --- a/src/Frame.cc +++ b/src/Frame.cc @@ -252,9 +252,9 @@ sdf::ElementPtr Frame::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Frame::SchemaFile() +inline std::string_view Frame::SchemaFile() { - static char kSchemaFile[] = "frame.sdf"; + static const char kSchemaFile[] = "frame.sdf"; return kSchemaFile; } diff --git a/src/Geometry.cc b/src/Geometry.cc index ec87a01da..b01d128cd 100644 --- a/src/Geometry.cc +++ b/src/Geometry.cc @@ -453,9 +453,9 @@ sdf::ElementPtr Geometry::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Geometry::SchemaFile() +inline std::string_view Geometry::SchemaFile() { - static char kSchemaFile[] = "geometry.sdf"; + static const char kSchemaFile[] = "geometry.sdf"; return kSchemaFile; } diff --git a/src/Gui.cc b/src/Gui.cc index 6f61d738e..405063eb1 100644 --- a/src/Gui.cc +++ b/src/Gui.cc @@ -149,9 +149,9 @@ sdf::Plugins &Gui::Plugins() ///////////////////////////////////////////////// -inline std::string_view Gui::SchemaFile() +inline std::string_view Gui::SchemaFile() { - static char kSchemaFile[] = "gui.sdf"; + static const char kSchemaFile[] = "gui.sdf"; return kSchemaFile; } diff --git a/src/Imu.cc b/src/Imu.cc index f239bcf1a..033575bec 100644 --- a/src/Imu.cc +++ b/src/Imu.cc @@ -456,9 +456,9 @@ sdf::ElementPtr Imu::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Imu::SchemaFile() +inline std::string_view Imu::SchemaFile() { - static char kSchemaFile[] = "imu.sdf"; + static const char kSchemaFile[] = "imu.sdf"; return kSchemaFile; } diff --git a/src/Joint.cc b/src/Joint.cc index 4532b5b41..2ce4042a0 100644 --- a/src/Joint.cc +++ b/src/Joint.cc @@ -632,9 +632,9 @@ void Joint::ClearSensors() } ///////////////////////////////////////////////// -inline std::string_view Joint::SchemaFile() +inline std::string_view Joint::SchemaFile() { - static char kSchemaFile[] = "joint.sdf"; + static const char kSchemaFile[] = "joint.sdf"; return kSchemaFile; } diff --git a/src/Lidar.cc b/src/Lidar.cc index 98b5caffe..2098d1c52 100644 --- a/src/Lidar.cc +++ b/src/Lidar.cc @@ -452,9 +452,9 @@ sdf::ElementPtr Lidar::ToElement() const } ///////////////////////////////////////////////// -inline std::string_view Lidar::SchemaFile() +inline std::string_view Lidar::SchemaFile() { - static char kSchemaFile[] = "lidar.sdf"; + static const char kSchemaFile[] = "lidar.sdf"; return kSchemaFile; } diff --git a/src/Light.cc b/src/Light.cc index ca44f3db1..d007ea2dd 100644 --- a/src/Light.cc +++ b/src/Light.cc @@ -558,9 +558,9 @@ sdf::ElementPtr Light::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Light::SchemaFile() +inline std::string_view Light::SchemaFile() { - static char kSchemaFile[] = "light.sdf"; + static const char kSchemaFile[] = "light.sdf"; return kSchemaFile; } diff --git a/src/Link.cc b/src/Link.cc index cc6522966..133bad813 100644 --- a/src/Link.cc +++ b/src/Link.cc @@ -1062,9 +1062,9 @@ sdf::ElementPtr Link::ToElement() const } ///////////////////////////////////////////////// -inline std::string_view Link::SchemaFile() +inline std::string_view Link::SchemaFile() { - static char kSchemaFile[] = "link.sdf"; + static const char kSchemaFile[] = "link.sdf"; return kSchemaFile; } diff --git a/src/Magnetometer.cc b/src/Magnetometer.cc index 580b41129..7c154d000 100644 --- a/src/Magnetometer.cc +++ b/src/Magnetometer.cc @@ -157,9 +157,9 @@ sdf::ElementPtr Magnetometer::ToElement() const } ///////////////////////////////////////////////// -inline std::string_view Magnetometer::SchemaFile() +inline std::string_view Magnetometer::SchemaFile() { - static char kSchemaFile[] = "magnetometer.sdf"; + static const char kSchemaFile[] = "magnetometer.sdf"; return kSchemaFile; } diff --git a/src/Material.cc b/src/Material.cc index 0d908aa1e..18f993f89 100644 --- a/src/Material.cc +++ b/src/Material.cc @@ -530,9 +530,9 @@ sdf::ElementPtr Material::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Material::SchemaFile() +inline std::string_view Material::SchemaFile() { - static char kSchemaFile[] = "material.sdf"; + static const char kSchemaFile[] = "material.sdf"; return kSchemaFile; } diff --git a/src/Model.cc b/src/Model.cc index 0cb50476a..e541ee6fc 100644 --- a/src/Model.cc +++ b/src/Model.cc @@ -1416,9 +1416,9 @@ sdf::Frame Model::PrepareForMerge(sdf::Errors &_errors, } ///////////////////////////////////////////////// -inline std::string_view Model::SchemaFile() +inline std::string_view Model::SchemaFile() { - static char kSchemaFile[] = "model.sdf"; + static const char kSchemaFile[] = "model.sdf"; return kSchemaFile; } diff --git a/src/NavSat.cc b/src/NavSat.cc index 7372ff994..35ad1c2b6 100644 --- a/src/NavSat.cc +++ b/src/NavSat.cc @@ -193,9 +193,9 @@ bool NavSat::operator!=(const NavSat &_navsat) const } ///////////////////////////////////////////////// -inline std::string_view NavSat::SchemaFile() +inline std::string_view NavSat::SchemaFile() { - static char kSchemaFile[] = "navsat.sdf"; + static const char kSchemaFile[] = "navsat.sdf"; return kSchemaFile; } diff --git a/src/Noise.cc b/src/Noise.cc index 4f6c8dc11..32ec382c5 100644 --- a/src/Noise.cc +++ b/src/Noise.cc @@ -302,9 +302,9 @@ sdf::ElementPtr Noise::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Noise::SchemaFile() +inline std::string_view Noise::SchemaFile() { - static char kSchemaFile[] = "noise.sdf"; + static const char kSchemaFile[] = "noise.sdf"; return kSchemaFile; } diff --git a/src/ParamPassing.cc b/src/ParamPassing.cc index 1b80d09cf..5bf0c97a9 100644 --- a/src/ParamPassing.cc +++ b/src/ParamPassing.cc @@ -52,7 +52,7 @@ void updateParams(const ParserConfig &_config, _errors.push_back({ErrorCode::ATTRIBUTE_MISSING, "Element identifier requires an element_id attribute, but the " "element_id is not set. Skipping element alteration:\n" - + ElementToString(childElemXml) + + ElementToString(_errors, childElemXml) }); continue; } @@ -67,7 +67,7 @@ void updateParams(const ParserConfig &_config, _errors.push_back({ErrorCode::ATTRIBUTE_INVALID, "Missing name after double colons in element identifier. " "Skipping element alteration:\n" - + ElementToString(childElemXml) + + ElementToString(_errors, childElemXml) }); continue; } @@ -83,7 +83,7 @@ void updateParams(const ParserConfig &_config, { _errors.push_back({ErrorCode::ATTRIBUTE_INVALID, "Action [" + actionStr + "] is not a valid action. Skipping " - "element alteration:\n" + ElementToString(childElemXml) + "element alteration:\n" + ElementToString(_errors, childElemXml) }); continue; } @@ -102,7 +102,7 @@ void updateParams(const ParserConfig &_config, _errors.push_back({ErrorCode::ATTRIBUTE_MISSING, "Element to be added is missing a 'name' attribute. " "Skipping element addition:\n" - + ElementToString(childElemXml) + + ElementToString(_errors, childElemXml) }); continue; } @@ -112,7 +112,7 @@ void updateParams(const ParserConfig &_config, { _errors.push_back({ErrorCode::ATTRIBUTE_INVALID, "The 'name' attribute can not be empty. Skipping element addition:\n" - + ElementToString(childElemXml) + + ElementToString(_errors, childElemXml) }); continue; } @@ -129,7 +129,7 @@ void updateParams(const ParserConfig &_config, + " element_id='" + childElemXml->Attribute("element_id") + "'> because element already exists in included model. " + "Skipping element addition:\n" - + ElementToString(childElemXml) + + ElementToString(_errors, childElemXml) }); continue; } @@ -157,7 +157,8 @@ void updateParams(const ParserConfig &_config, _errors.push_back({ErrorCode::ELEMENT_MISSING, "Could not find element <" + std::string(childElemXml->Name()) + " element_id='" + childElemXml->Attribute("element_id") + "'>. " + - "Skipping element modification:\n" + ElementToString(childElemXml) + "Skipping element modification:\n" + + ElementToString(_errors, childElemXml) }); continue; } @@ -194,7 +195,7 @@ void updateParams(const ParserConfig &_config, { _errors.push_back({ErrorCode::ELEMENT_INVALID, "Unable to convert XML to SDF. Skipping element replacement:\n" - + ElementToString(childElemXml) + + ElementToString(_errors, childElemXml) }); continue; } @@ -355,7 +356,7 @@ ElementPtr initElementDescription(const tinyxml2::XMLElement *_xml, _errors.push_back({ErrorCode::ELEMENT_INVALID, "Element [" + std::string(_xml->Name()) + "] is not a defined " "SDF element. Skipping element alteration\n: " - + ElementToString(_xml) + + ElementToString(_errors, _xml) }); return nullptr; } @@ -384,7 +385,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "Missing an action attribute. Skipping child element modification " "with parent <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) + "'>:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); continue; } @@ -397,7 +398,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "child element modification with parent <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) + "'>:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); continue; } @@ -411,7 +412,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "Could not find element. Skipping child element removal " "with parent <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) + "'>:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); } else @@ -430,7 +431,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "Could not find element. Skipping child element modification " "with parent <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) + "'>:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); } else @@ -455,7 +456,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "child element modification with parent <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) + "'>:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); continue; } @@ -468,7 +469,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "Unable to convert XML to SDF. Skipping child element alteration " "with parent <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) + "'>:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); continue; } @@ -486,7 +487,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "Could not find element. Skipping child element replacement " "with parent <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) + "'>:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); continue; } @@ -498,7 +499,7 @@ void handleIndividualChildActions(const ParserConfig &_config, "Replacement element is missing a 'name' attribute. " "Skipping element replacement <" + std::string(_childrenXml->Name()) + " element_id='" + std::string(_childrenXml->Attribute("element_id")) - + "'>:\n" + ElementToString(xmlChild) + + "'>:\n" + ElementToString(_errors, xmlChild) }); continue; } @@ -525,7 +526,7 @@ void add(const ParserConfig &_config, const std::string &_source, { _errors.push_back({ErrorCode::ELEMENT_INVALID, "Unable to convert XML to SDF. Skipping element addition:\n" - + ElementToString(_childXml) + + ElementToString(_errors, _childXml) }); } } @@ -557,7 +558,8 @@ void modifyAttributes(tinyxml2::XMLElement *_xml, { _errors.push_back({ErrorCode::ATTRIBUTE_INVALID, "Attribute [" + attrName + "] is invalid. " - "Skipping attribute modification in:\n" + ElementToString(_xml) + "Skipping attribute modification in:\n" + + ElementToString(_errors, _xml) }); continue; } @@ -582,7 +584,7 @@ void modifyChildren(tinyxml2::XMLElement *_xml, { _errors.push_back({ErrorCode::ELEMENT_MISSING, "Could not find element [" + elemName + "]. " - "Skipping modification for:\n" + ElementToString(_xml) + "Skipping modification for:\n" + ElementToString(_errors, _xml) }); continue; } @@ -599,7 +601,7 @@ void modifyChildren(tinyxml2::XMLElement *_xml, _errors.push_back({ErrorCode::ELEMENT_INVALID, "Value [" + std::string(xmlChild->GetText()) + "] for element [" + elemName + "] is invalid. Skipping modification for:\n" - + ElementToString(_xml) + + ElementToString(_errors, _xml) }); continue; } @@ -620,9 +622,9 @@ void modifyChildren(tinyxml2::XMLElement *_xml, // sdf has child elements but no children were specified in xml std::stringstream ss; ss << "No modifications for element " - << ElementToString(xmlChild) + << ElementToString(_errors, xmlChild) << " provided, skipping modification for:\n" - << ElementToString(_xml); + << ElementToString(_errors, _xml); Error err(ErrorCode::WARNING, ss.str()); enforceConfigurablePolicyCondition( _config.WarningsPolicy(), err, _errors); @@ -650,7 +652,7 @@ void modify(tinyxml2::XMLElement *_xml, const sdf::ParserConfig &_config, _errors.push_back({ErrorCode::ELEMENT_INVALID, "Value [" + std::string(_xml->GetText()) + "] for element [" + std::string(_xml->Name()) + "] is invalid. Skipping modification for:\n" - + ElementToString(_xml) + + ElementToString(_errors, _xml) }); } } @@ -688,7 +690,7 @@ void remove(const tinyxml2::XMLElement *_xml, const sdf::ParserConfig &_config, + std::string(xmlParent->Name()) + " element_id='" + std::string(xmlParent->Attribute("element_id")) + "'> with parent <" + std::string(_xml->Name()) + ">:\n" - + ElementToString(xmlChild) + + ElementToString(_errors, xmlChild) }); continue; } diff --git a/src/ParticleEmitter.cc b/src/ParticleEmitter.cc index 86bb64ab7..05809f548 100644 --- a/src/ParticleEmitter.cc +++ b/src/ParticleEmitter.cc @@ -575,9 +575,9 @@ sdf::ElementPtr ParticleEmitter::ToElement(sdf::Errors &_errors) const ///////////////////////////////////////////////// -inline std::string_view ParticleEmitter::SchemaFile() +inline std::string_view ParticleEmitter::SchemaFile() { - static char kSchemaFile[] = "particle_emitter.sdf"; + static const char kSchemaFile[] = "particle_emitter.sdf"; return kSchemaFile; } diff --git a/src/Physics.cc b/src/Physics.cc index c1ca03488..50b0d726f 100644 --- a/src/Physics.cc +++ b/src/Physics.cc @@ -234,9 +234,9 @@ sdf::ElementPtr Physics::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Physics::SchemaFile() +inline std::string_view Physics::SchemaFile() { - static char kSchemaFile[] = "physics.sdf"; + static const char kSchemaFile[] = "physics.sdf"; return kSchemaFile; } diff --git a/src/Plugin.cc b/src/Plugin.cc index 637f12334..78e1de990 100644 --- a/src/Plugin.cc +++ b/src/Plugin.cc @@ -313,9 +313,9 @@ bool Plugin::operator!=(const Plugin &_plugin) const } ///////////////////////////////////////////////// -inline std::string_view Plugin::SchemaFile() +inline std::string_view Plugin::SchemaFile() { - static char kSchemaFile[] = "plugin.sdf"; + static const char kSchemaFile[] = "plugin.sdf"; return kSchemaFile; } diff --git a/src/Projector.cc b/src/Projector.cc index 8dbbf6457..555b1cdd7 100644 --- a/src/Projector.cc +++ b/src/Projector.cc @@ -333,9 +333,9 @@ sdf::ElementPtr Projector::ToElement() const } ///////////////////////////////////////////////// -inline std::string_view Projector::SchemaFile() +inline std::string_view Projector::SchemaFile() { - static char kSchemaFile[] = "projector.sdf"; + static const char kSchemaFile[] = "projector.sdf"; return kSchemaFile; } diff --git a/src/Root.cc b/src/Root.cc index 2449b5a49..1831e91fa 100644 --- a/src/Root.cc +++ b/src/Root.cc @@ -646,9 +646,9 @@ sdf::ElementPtr Root::ToElement(const OutputConfig &_config) const } ///////////////////////////////////////////////// -inline std::string_view Root::SchemaFile() +inline std::string_view Root::SchemaFile() { - static char kSchemaFile[] = "root.sdf"; + static const char kSchemaFile[] = "root.sdf"; return kSchemaFile; } diff --git a/src/SDF.cc b/src/SDF.cc index 7a43d0f9a..e033aac2b 100644 --- a/src/SDF.cc +++ b/src/SDF.cc @@ -27,11 +27,13 @@ #include "sdf/parser.hh" #include "sdf/Assert.hh" #include "sdf/Console.hh" +#include "sdf/Error.hh" #include "sdf/Filesystem.hh" #include "sdf/SDFImpl.hh" #include "SDFImplPrivate.hh" #include "sdf/sdf_config.h" #include "EmbeddedSdf.hh" +#include "Utils.hh" #include @@ -61,14 +63,40 @@ void setFindCallback(std::function _cb) ///////////////////////////////////////////////// std::string findFile( const std::string &_filename, bool _searchLocalPath, bool _useCallback) +{ + sdf::Errors errors; + std::string result = findFile(errors, _filename, _searchLocalPath, + _useCallback, ParserConfig::GlobalConfig()); + sdf::throwOrPrintErrors(errors); + return result; +} + +///////////////////////////////////////////////// +std::string findFile( + sdf::Errors &_errors, const std::string &_filename, bool _searchLocalPath, + bool _useCallback) { return findFile( - _filename, _searchLocalPath, _useCallback, ParserConfig::GlobalConfig()); + _errors, _filename, _searchLocalPath, + _useCallback, ParserConfig::GlobalConfig()); } ///////////////////////////////////////////////// std::string findFile(const std::string &_filename, bool _searchLocalPath, bool _useCallback, const ParserConfig &_config) +{ + sdf::Errors errors; + std::string result = findFile(errors, _filename, _searchLocalPath, + _useCallback, _config); + sdf::throwOrPrintErrors(errors); + return result; +} + + +///////////////////////////////////////////////// +std::string findFile(sdf::Errors &_errors, const std::string &_filename, + bool _searchLocalPath, bool _useCallback, + const ParserConfig &_config) { // Check to see if _filename is URI. If so, resolve the URI path. for (const auto &[uriScheme, paths] : _config.URIPathMap()) @@ -162,8 +190,9 @@ std::string findFile(const std::string &_filename, bool _searchLocalPath, { if (!_config.FindFileCallback()) { - sdferr << "Tried to use callback in sdf::findFile(), but the callback " - "is empty. Did you call sdf::setFindCallback()?\n"; + _errors.push_back({sdf::ErrorCode::FILE_READ, + "Tried to use callback in sdf::findFile(), but the callback " + "is empty. Did you call sdf::setFindCallback()?"}); return std::string(); } else @@ -195,13 +224,29 @@ SDF::~SDF() ///////////////////////////////////////////////// void SDF::PrintDescription() { - this->Root()->PrintDescription(""); + sdf::Errors errors; + this->PrintDescription(errors); + sdf::throwOrPrintErrors(errors); +} + +///////////////////////////////////////////////// +void SDF::PrintDescription(sdf::Errors &_errors) +{ + this->Root()->PrintDescription(_errors, ""); } ///////////////////////////////////////////////// void SDF::PrintValues(const PrintConfig &_config) { - this->Root()->PrintValues("", _config); + sdf::Errors errors; + this->PrintValues(errors, _config); + sdf::throwOrPrintErrors(errors); +} + +///////////////////////////////////////////////// +void SDF::PrintValues(sdf::Errors &_errors, const PrintConfig &_config) +{ + this->Root()->PrintValues(_errors, "", _config); } ///////////////////////////////////////////////// @@ -319,13 +364,22 @@ void SDF::PrintDoc() ///////////////////////////////////////////////// void SDF::Write(const std::string &_filename) { - std::string string = this->Root()->ToString(""); + sdf::Errors errors; + this->Write(errors, _filename); + sdf::throwOrPrintErrors(errors); +} + +///////////////////////////////////////////////// +void SDF::Write(sdf::Errors &_errors, const std::string &_filename) +{ + std::string string = this->Root()->ToString(_errors, ""); std::ofstream out(_filename.c_str(), std::ios::out); if (!out) { - sdferr << "Unable to open file[" << _filename << "] for writing\n"; + _errors.push_back({sdf::ErrorCode::FILE_READ, + "Unable to open file[" + _filename + "] for writing."}); return; } out << string; @@ -334,6 +388,16 @@ void SDF::Write(const std::string &_filename) ///////////////////////////////////////////////// std::string SDF::ToString(const PrintConfig &_config) const +{ + sdf::Errors errors; + std::string result = this->ToString(errors, _config); + sdf::throwOrPrintErrors(errors); + return result; +} + +///////////////////////////////////////////////// +std::string SDF::ToString(sdf::Errors &_errors, + const PrintConfig &_config) const { std::ostringstream stream; @@ -343,7 +407,7 @@ std::string SDF::ToString(const PrintConfig &_config) const stream << "\n"; } - stream << this->Root()->ToString("", _config); + stream << this->Root()->ToString(_errors, "", _config); if (this->Root()->GetName() != "sdf") { @@ -355,11 +419,21 @@ std::string SDF::ToString(const PrintConfig &_config) const ///////////////////////////////////////////////// void SDF::SetFromString(const std::string &_sdfData) +{ + sdf::Errors errors; + this->SetFromString(errors, _sdfData); + sdf::throwOrPrintErrors(errors); +} + +///////////////////////////////////////////////// +void SDF::SetFromString(sdf::Errors &_errors, + const std::string &_sdfData) { sdf::initFile("root.sdf", this->Root()); - if (!sdf::readString(_sdfData, this->Root())) + if (!sdf::readString(_sdfData, this->Root(), _errors)) { - sdferr << "Unable to parse sdf string[" << _sdfData << "]\n"; + _errors.push_back({sdf::ErrorCode::PARSING_ERROR, + "Unable to parse sdf string[" + _sdfData + "]"}); } } @@ -429,12 +503,21 @@ void SDF::Version(const std::string &_version) ///////////////////////////////////////////////// ElementPtr SDF::WrapInRoot(const ElementPtr &_sdf) +{ + sdf::Errors errors; + ElementPtr result = SDF::WrapInRoot(errors, _sdf); + sdf::throwOrPrintErrors(errors); + return result; +} + +///////////////////////////////////////////////// +ElementPtr SDF::WrapInRoot(sdf::Errors &_errors, const ElementPtr &_sdf) { ElementPtr root(new Element); root->SetName("sdf"); std::stringstream v; v << Version(); - root->AddAttribute("version", "string", v.str(), true, "version"); + root->AddAttribute("version", "string", v.str(), true, _errors, "version"); root->InsertElement(_sdf->Clone()); return root; } @@ -442,6 +525,19 @@ ElementPtr SDF::WrapInRoot(const ElementPtr &_sdf) ///////////////////////////////////////////////// const std::string &SDF::EmbeddedSpec( const std::string &_filename, const bool _quiet) +{ + sdf::Errors errors; + const std::string &result = EmbeddedSpec(errors, _filename); + if (!_quiet) + { + sdf::throwOrPrintErrors(errors); + } + return result; +} + +///////////////////////////////////////////////// +const std::string &SDF::EmbeddedSpec( + sdf::Errors &_errors, const std::string &_filename) { try { @@ -450,9 +546,9 @@ const std::string &SDF::EmbeddedSpec( } catch(const std::out_of_range &) { - if (!_quiet) - sdferr << "Unable to find SDF filename[" << _filename << "] with " - << "version " << SDF::Version() << "\n"; + _errors.push_back({sdf::ErrorCode::FILE_READ, + "Unable to find SDF filename[" + _filename + "] with " + + "version " + SDF::Version()}); } // An empty SDF string is returned if a query into the embeddedSdf map fails. diff --git a/src/SDF_TEST.cc b/src/SDF_TEST.cc index 44bb9f284..8c8289a9d 100644 --- a/src/SDF_TEST.cc +++ b/src/SDF_TEST.cc @@ -742,6 +742,46 @@ bool create_new_temp_dir(std::string &_new_temp_path) return true; } +///////////////////////////////////////////////// +TEST(SDF, ErrorOutput) +{ + std::stringstream buffer; + sdf::testing::RedirectConsoleStream redir( + sdf::Console::Instance()->GetMsgStream(), &buffer); + + #ifdef _WIN32 + sdf::Console::Instance()->SetQuiet(false); + sdf::testing::ScopeExit revertSetQuiet( + [] + { + sdf::Console::Instance()->SetQuiet(true); + }); + #endif + sdf::Errors errors; + + // Test findFile + EXPECT_EQ(sdf::findFile(errors, "adfjialkas31", false, true), ""); + EXPECT_EQ(errors.size(), 1) << errors; + EXPECT_NE(std::string::npos, + errors[0].Message().find("Tried to use callback in sdf::findFile(), " + "but the callback is empty. Did you call " + "sdf::setFindCallback()?")) + << errors[0].Message(); + errors.clear(); + + sdf::SDF sdf; + sdf.SetFromString(errors, "banana"); + EXPECT_EQ(errors.size(), 2) << errors; + EXPECT_NE(std::string::npos, + errors[0].Message().find("Error parsing XML from string")) + << errors[0].Message(); + EXPECT_NE(std::string::npos, + errors[1].Message().find("Unable to parse sdf string[banana]")) + << errors[1].Message(); + // Check nothing has been printed + EXPECT_TRUE(buffer.str().empty()) << buffer.str(); +} + ///////////////////////////////////////////////// bool g_findFileCbCalled = false; std::string findFileCb(const std::string &) diff --git a/src/Scene.cc b/src/Scene.cc index cf4fcfcd9..c1b7ef2e6 100644 --- a/src/Scene.cc +++ b/src/Scene.cc @@ -214,9 +214,9 @@ sdf::ElementPtr Scene::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Scene::SchemaFile() +inline std::string_view Scene::SchemaFile() { - static char kSchemaFile[] = "scene.sdf"; + static const char kSchemaFile[] = "scene.sdf"; return kSchemaFile; } diff --git a/src/Sensor.cc b/src/Sensor.cc index 4a2ad4864..fcca80fbe 100644 --- a/src/Sensor.cc +++ b/src/Sensor.cc @@ -850,9 +850,9 @@ void Sensor::AddPlugin(const Plugin &_plugin) } ///////////////////////////////////////////////// -inline std::string_view Sensor::SchemaFile() +inline std::string_view Sensor::SchemaFile() { - static char kSchemaFile[] = "sensor.sdf"; + static const char kSchemaFile[] = "sensor.sdf"; return kSchemaFile; } diff --git a/src/Surface.cc b/src/Surface.cc index 7772583e6..c8b20e775 100644 --- a/src/Surface.cc +++ b/src/Surface.cc @@ -772,9 +772,9 @@ sdf::ElementPtr Surface::ToElement(sdf::Errors &_errors) const } ///////////////////////////////////////////////// -inline std::string_view Surface::SchemaFile() +inline std::string_view Surface::SchemaFile() { - static char kSchemaFile[] = "surface.sdf"; + static const char kSchemaFile[] = "surface.sdf"; return kSchemaFile; } diff --git a/src/Visual.cc b/src/Visual.cc index 9682439ec..eba6d018e 100644 --- a/src/Visual.cc +++ b/src/Visual.cc @@ -380,9 +380,9 @@ void Visual::AddPlugin(const Plugin &_plugin) } ///////////////////////////////////////////////// -inline std::string_view Visual::SchemaFile() +inline std::string_view Visual::SchemaFile() { - static char kSchemaFile[] = "visual.sdf"; + static const char kSchemaFile[] = "visual.sdf"; return kSchemaFile; } diff --git a/src/World.cc b/src/World.cc index 4a884f6fb..275b55e59 100644 --- a/src/World.cc +++ b/src/World.cc @@ -773,14 +773,27 @@ Actor *World::ActorByIndex(uint64_t _index) ///////////////////////////////////////////////// bool World::ActorNameExists(const std::string &_name) const { - for (auto const &a : this->dataPtr->actors) + return nullptr != this->ActorByName(_name); +} + +///////////////////////////////////////////////// +const Actor *World::ActorByName(const std::string &_name) const +{ + for (const Actor &a : this->dataPtr->actors) { if (a.Name() == _name) { - return true; + return &a; } } - return false; + return nullptr; +} + +///////////////////////////////////////////////// +Actor *World::ActorByName(const std::string &_name) +{ + return const_cast( + static_cast(this)->ActorByName(_name)); } ////////////////////////////////////////////////// @@ -1273,9 +1286,9 @@ void World::AddPlugin(const Plugin &_plugin) } ///////////////////////////////////////////////// -inline std::string_view World::SchemaFile() +inline std::string_view World::SchemaFile() { - static char kSchemaFile[] = "world.sdf"; + static const char kSchemaFile[] = "world.sdf"; return kSchemaFile; } diff --git a/src/World_TEST.cc b/src/World_TEST.cc index 3100ce166..ad1e0d2d5 100644 --- a/src/World_TEST.cc +++ b/src/World_TEST.cc @@ -64,6 +64,9 @@ TEST(DOMWorld, Construction) EXPECT_EQ(nullptr, world.ModelByName("a::b::c")); EXPECT_EQ(nullptr, world.ModelByName("::::")); + EXPECT_EQ(nullptr, world.ActorByName("")); + EXPECT_EQ(nullptr, world.ActorByName("default")); + EXPECT_EQ(0u, world.FrameCount()); EXPECT_EQ(nullptr, world.FrameByIndex(0)); EXPECT_EQ(nullptr, world.FrameByIndex(1)); @@ -458,15 +461,27 @@ TEST(DOMWorld, AddActor) EXPECT_EQ(1u, world.ActorCount()); EXPECT_FALSE(world.AddActor(actor)); EXPECT_EQ(1u, world.ActorCount()); + EXPECT_NE(nullptr, world.ActorByName("actor1")); world.ClearActors(); EXPECT_EQ(0u, world.ActorCount()); + EXPECT_EQ(nullptr, world.ActorByName("actor1")); EXPECT_TRUE(world.AddActor(actor)); EXPECT_EQ(1u, world.ActorCount()); const sdf::Actor *actorFromWorld = world.ActorByIndex(0); ASSERT_NE(nullptr, actorFromWorld); EXPECT_EQ(actorFromWorld->Name(), actor.Name()); + + const sdf::Actor *actorFromWorldByName = world.ActorByName("actor1"); + ASSERT_NE(nullptr, actorFromWorldByName); + EXPECT_EQ(actorFromWorldByName->Name(), actor.Name()); + + sdf::Actor *mutableActorFromWorldByName = world.ActorByName("actor1"); + ASSERT_NE(nullptr, mutableActorFromWorldByName); + EXPECT_EQ(mutableActorFromWorldByName->Name(), actor.Name()); + mutableActorFromWorldByName->SetName("new_name"); + EXPECT_NE(mutableActorFromWorldByName->Name(), actor.Name()); } ///////////////////////////////////////////////// diff --git a/src/XmlUtils.cc b/src/XmlUtils.cc index 4dbceab71..5d3709707 100644 --- a/src/XmlUtils.cc +++ b/src/XmlUtils.cc @@ -15,7 +15,9 @@ * */ #include +#include +#include "Utils.hh" #include "XmlUtils.hh" #include "sdf/Console.hh" @@ -27,27 +29,41 @@ inline namespace SDF_VERSION_NAMESPACE { ///////////////////////////////////////////////// tinyxml2::XMLNode *DeepClone(tinyxml2::XMLDocument *_doc, const tinyxml2::XMLNode *_src) +{ + sdf::Errors errors; + tinyxml2::XMLNode *result = DeepClone(errors, _doc, _src); + sdf::throwOrPrintErrors(errors); + return result; +} + +///////////////////////////////////////////////// +tinyxml2::XMLNode *DeepClone(sdf::Errors &_errors, + tinyxml2::XMLDocument *_doc, + const tinyxml2::XMLNode *_src) { if (_src == nullptr) { - sdferr << "Pointer to XML node _src is NULL\n"; + _errors.push_back({sdf::ErrorCode::XML_ERROR, + "Pointer to XML node _src is NULL"}); return nullptr; } tinyxml2::XMLNode *copy = _src->ShallowClone(_doc); if (copy == nullptr) { - sdferr << "Failed to clone node " << _src->Value() << "\n"; + _errors.push_back({sdf::ErrorCode::XML_ERROR, + "Failed to clone node " + std::string(_src->Value())}); return nullptr; } for (const tinyxml2::XMLNode *node = _src->FirstChild(); node != nullptr; node = node->NextSibling()) { - auto *childCopy = DeepClone(_doc, node); + auto *childCopy = DeepClone(_errors, _doc, node); if (childCopy == nullptr) { - sdferr << "Failed to clone child " << node->Value() << "\n"; + _errors.push_back({sdf::ErrorCode::XML_ERROR, + "Failed to clone child " + std::string(node->Value())}); return nullptr; } copy->InsertEndChild(childCopy); @@ -57,11 +73,13 @@ tinyxml2::XMLNode *DeepClone(tinyxml2::XMLDocument *_doc, } ///////////////////////////////////////////////// -std::string ElementToString(const tinyxml2::XMLElement *_elem) +std::string ElementToString(sdf::Errors &_errors, + const tinyxml2::XMLElement *_elem) { if (_elem == nullptr) { - sdferr << "Pointer to XML Element _elem is nullptr\n"; + _errors.push_back({sdf::ErrorCode::XML_ERROR, + "Pointer to XML Element _elem is nullptr"}); return ""; } diff --git a/src/XmlUtils.hh b/src/XmlUtils.hh index 7ad0c30d4..522458aad 100644 --- a/src/XmlUtils.hh +++ b/src/XmlUtils.hh @@ -41,10 +41,26 @@ namespace sdf tinyxml2::XMLNode *DeepClone(tinyxml2::XMLDocument *_doc, const tinyxml2::XMLNode *_src); + /// \brief Perform a deep copy of an XML Node + /// + /// This copies an XMLNode _src and all of its decendants + /// into a specified XMLDocument. + /// + /// \param[out] _errors Vector of errors + /// \param[in] _doc Document in which to place the copied node + /// \param[in] _src The node to deep copy + /// \returns The newly copied node upon success OR + /// nullptr if an error occurs. + tinyxml2::XMLNode *DeepClone(sdf::Errors &_errors, + tinyxml2::XMLDocument *_doc, + const tinyxml2::XMLNode *_src); + /// \brief Converts the XML Element to a string + /// \param[out] _errors Vector of errors /// \param[in] _elem Element to be converted /// \return The string representation - std::string ElementToString(const tinyxml2::XMLElement *_elem); + std::string ElementToString(sdf::Errors &_errors, + const tinyxml2::XMLElement *_elem); } } #endif diff --git a/src/XmlUtils_TEST.cc b/src/XmlUtils_TEST.cc index aef79262d..4190b058b 100644 --- a/src/XmlUtils_TEST.cc +++ b/src/XmlUtils_TEST.cc @@ -37,7 +37,9 @@ TEST(XMLUtils, DeepClone) ASSERT_EQ(tinyxml2::XML_SUCCESS, ret); auto root = oldDoc.FirstChild(); - auto newRoot = sdf::DeepClone(&newDoc, root); + sdf::Errors errors; + auto newRoot = sdf::DeepClone(errors, &newDoc, root); + EXPECT_TRUE(errors.empty()) << errors; EXPECT_STREQ("document", newRoot->ToElement()->Name()); @@ -53,3 +55,48 @@ TEST(XMLUtils, DeepClone) auto childB_text = newChildB->ToElement()->GetText(); EXPECT_STREQ("Hello World", childB_text); } + +///////////////////////////////////////////////// +TEST(XMLUtils, InvalidDeepClone) +{ + sdf::Errors errors; + auto newRoot = sdf::DeepClone(errors, nullptr, nullptr); + EXPECT_EQ(nullptr, newRoot); + EXPECT_EQ(1u, errors.size()) << errors; + ASSERT_FALSE(errors.empty()); + EXPECT_EQ(errors[0].Code(), sdf::ErrorCode::XML_ERROR); +} + +///////////////////////////////////////////////// +TEST(XMLUtils, ElementToString) +{ + tinyxml2::XMLDocument doc; + + std::string docXml = +R"( + + Hello World + + +)"; + + auto ret = doc.Parse(docXml.c_str()); + ASSERT_EQ(tinyxml2::XML_SUCCESS, ret); + + auto root = doc.FirstChild(); + sdf::Errors errors; + std::string docString = sdf::ElementToString(errors, root->ToElement()); + EXPECT_TRUE(errors.empty()) << errors; + EXPECT_EQ(docXml, docString); +} + +///////////////////////////////////////////////// +TEST(XMLUtils, InvalidElementToString) +{ + sdf::Errors errors; + std::string docString = sdf::ElementToString(errors, nullptr); + EXPECT_TRUE(docString.empty()); + EXPECT_EQ(1u, errors.size()) << errors; + ASSERT_FALSE(errors.empty()); + EXPECT_EQ(errors[0].Code(), sdf::ErrorCode::XML_ERROR); +}