Support FilePaths instead of raw strings in XML code.

PiperOrigin-RevId: 655177026
Change-Id: I55b055551384b01ba7a4baa5d75fbe3329b7d25d
This commit is contained in:
Kyle Bayes
2024-07-23 08:35:56 -07:00
committed by Copybara-Service
parent 2d24c58819
commit 2746cb8559
10 changed files with 195 additions and 179 deletions
+1 -1
View File
@@ -996,7 +996,7 @@ std::string FilePath::PathReduce(const std::string& str) {
if (IsSeperator(str[i])) {
std::string temp = str.substr(j, i - j);
j = i + 1;
if (temp == ".." && !dirs.empty()) {
if (temp == ".." && !dirs.empty() && dirs.back() != "..") {
dirs.pop_back();
} else if (temp != ".") {
dirs.push_back(std::move(temp));
+65 -78
View File
@@ -48,9 +48,11 @@ namespace {
using tinyxml2::XMLDocument;
using tinyxml2::XMLElement;
using tinyxml2::XMLNode;
using mujoco::user::FilePath;
namespace mju = ::mujoco::util;
// We are using "locale-sensitive" sprintf to read and write XML.
// When MuJoCo is being used as a plug-in for an application that respects the system locale
// (e.g. Unity), the user's locale setting can affect the formatting of numbers into strings.
@@ -97,29 +99,30 @@ class LocaleOverride {
};
#endif
} // namespace
// Main writer function - calls mjXWrite
std::string mjWriteXML(const mjSpec* spec, char* error, int error_sz) {
LocaleOverride locale_override;
// check for empty model
if (!spec) {
mjCopyError(error, "Cannot write empty model", error_sz);
return "";
void RegisterResourceProvider() {
// register string resource provider if not registered before
if (mjp_getResourceProvider("LoadModelFromString:") == nullptr) {
mjpResourceProvider resourceProvider;
mjp_defaultResourceProvider(&resourceProvider);
resourceProvider.prefix = "LoadModelFromString";
resourceProvider.open = +[](mjResource* resource) {
resource->data = &(resource->name[strlen("LoadModelFromString:")]);
return 1;
};
resourceProvider.read =
+[](mjResource* resource, const void** buffer) {
*buffer = resource->data;
return (int) strlen((const char*) resource->data);
};
resourceProvider.close = +[](mjResource* resource) {};
mjp_registerResourceProvider(&resourceProvider);
}
mjXWriter writer;
writer.SetModel(spec);
return writer.Write(error, error_sz);
}
// find include elements recursively, replace them with subtree from xml file
static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
std::string_view dir, const mjVFS* vfs,
std::unordered_set<std::string>& included) {
void IncludeXML(mjXReader& reader, XMLElement* elem,
const FilePath& dir, const mjVFS* vfs,
std::unordered_set<std::string>& included) {
// capture directory defaults on first pass of XML tree
if (!strcasecmp(elem->Value(), "compiler")) {
auto assetdir_attr = mjXUtil::ReadAttrStr(elem, "assetdir");
@@ -142,7 +145,7 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
if (strcasecmp(elem->Value(), "include")) {
XMLElement* child = elem->FirstChildElement();
for (; child; child = child->NextSiblingElement()) {
mjIncludeXML(reader, child, dir, vfs, included);
IncludeXML(reader, child, dir, vfs, included);
}
return;
}
@@ -153,26 +156,22 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
}
// get filename
auto file_attr = mjXUtil::ReadAttrStr(elem, "file", true);
auto file_attr = mjXUtil::ReadAttrFile(elem, "file", reader.ModelFileDir(),
true);
if (!file_attr.has_value()) {
throw mjXError(elem, "Include element missing file attribute");
}
std::string filename = file_attr.value();
FilePath filename = file_attr.value();
// block repeated include files
if (included.find(filename) != included.end()) {
if (included.find(filename.Str()) != included.end()) {
throw mjXError(elem, "File '%s' already included", filename.c_str());
}
// TODO: b/325905702 - We have a messy wrapper here to remain backwards
// compatible, which will be removed in the near future.
std::string fullname;
if (!mjuu_isabspath(filename)) {
fullname = reader.ModelFileDir() + filename;
} else {
fullname = filename;
}
FilePath fullname = reader.ModelFileDir() + filename;
// legacy behavior: try to load in top level directory
std::array<char, 1024> error;
@@ -180,8 +179,8 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
error.data(), error.size());
if (resource == nullptr) {
// new behavior: try to load in relative directory
if (!mjuu_isabspath(filename)) {
fullname = std::string(dir) + filename;
if (!filename.IsAbs()) {
fullname = dir + filename;
resource = mju_openResource(fullname.c_str(), vfs, error.data(), error.size());
}
}
@@ -190,18 +189,14 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
throw mjXError(elem, "%s", error.data());
}
if (!mjuu_isabspath(filename)) {
filename = std::string(dir) + filename;
}
filename = dir + filename;
const char* include_dir = nullptr;
int ninclude_dir = 0;
mju_getResourceDir(resource, &include_dir, &ninclude_dir);
std::string next_dir = std::string(include_dir, ninclude_dir);
if (!mjuu_isabspath(filename)) {
next_dir = std::string(dir) + next_dir;
}
elem->SetAttribute("dir", next_dir.data());
FilePath next_dir = FilePath(std::string(include_dir, ninclude_dir));
next_dir = dir + next_dir;
elem->SetAttribute("dir", next_dir.c_str());
const char* xmlstring = nullptr;
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
@@ -228,7 +223,7 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
}
// remember that file was included
included.insert(filename);
included.insert(filename.Str());
// get and check root element
XMLElement* docroot = doc.RootElement();
@@ -262,21 +257,21 @@ static void mjIncludeXML(mjXReader& reader, XMLElement* elem,
// recursively run include
child = include->FirstChildElement();
for (; child; child = child->NextSiblingElement()) {
mjIncludeXML(reader, child, next_dir, vfs, included);
IncludeXML(reader, child, next_dir, vfs, included);
}
}
} // namespace
// Main parser function
mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
char* error, int error_sz) {
mjSpec* ParseXML(const char* filename, const mjVFS* vfs,
char* error, int nerror) {
LocaleOverride locale_override;
// check arguments
if (!filename) {
if (error) {
std::snprintf(error, error_sz, "mjParseXML: filename argument required\n");
std::snprintf(error, nerror, "ParseXML: filename argument required\n");
}
return nullptr;
}
@@ -293,21 +288,21 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
mjResource* resource = mju_openResource(filename, vfs,
rerror.data(), rerror.size());
if (resource == nullptr) {
std::snprintf(error, error_sz, "mjParseXML: %s", rerror.data());
std::snprintf(error, nerror, "ParseXML: %s", rerror.data());
return nullptr;
}
int buffer_size = mju_readResource(resource, (const void**) &xmlstring);
if (buffer_size < 0) {
if (error) {
std::snprintf(error, error_sz,
"mjParseXML: error reading file '%s'", filename);
std::snprintf(error, nerror,
"ParseXML: error reading file '%s'", filename);
}
mju_closeResource(resource);
return nullptr;
} else if (!buffer_size) {
if (error) {
std::snprintf(error, error_sz, "mjParseXML: empty file '%s'", filename);
std::snprintf(error, nerror, "ParseXML: empty file '%s'", filename);
}
mju_closeResource(resource);
return nullptr;
@@ -321,7 +316,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
// error checking
if (doc.Error()) {
if (error) {
snprintf(error, error_sz, "XML parse error %d:\n%s\n",
snprintf(error, nerror, "XML parse error %d:\n%s\n",
doc.ErrorID(), doc.ErrorStr());
}
mju_closeResource(resource);
@@ -332,7 +327,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
XMLElement* root = doc.RootElement();
if (!root) {
mju_closeResource(resource);
mjCopyError(error, "XML root element not found", error_sz);
mjCopyError(error, "XML root element not found", nerror);
return nullptr;
}
@@ -357,7 +352,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
std::unordered_set<std::string> included = {filename};
mjXReader parser;
parser.SetModelFileDir(mjs_getString(spec->modelfiledir));
mjIncludeXML(parser, root, mjs_getString(spec->modelfiledir), vfs, included);
IncludeXML(parser, root, parser.ModelFileDir(), vfs, included);
// parse MuJoCo model
parser.SetModel(spec);
@@ -385,7 +380,7 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
// catch known errors
catch (mjXError err) {
mjCopyError(error, err.message, error_sz);
mjCopyError(error, err.message, nerror);
mj_deleteSpec(spec);
return nullptr;
}
@@ -393,32 +388,24 @@ mjSpec* mjParseXML(const char* filename, const mjVFS* vfs,
return spec;
}
static void RegisterResourceProvider() {
// register string resource provider if not registered before
if (mjp_getResourceProvider("LoadModelFromString:") == nullptr) {
mjpResourceProvider resourceProvider;
mjp_defaultResourceProvider(&resourceProvider);
resourceProvider.prefix = "LoadModelFromString";
resourceProvider.open = +[](mjResource* resource) {
resource->data = &(resource->name[strlen("LoadModelFromString:")]);
return 1;
};
resourceProvider.read =
+[](mjResource* resource, const void** buffer) {
*buffer = resource->data;
return (int) strlen((const char*) resource->data);
};
resourceProvider.close = +[](mjResource* resource) {};
mjp_registerResourceProvider(&resourceProvider);
}
}
mjSpec* ParseSpecFromString(std::string_view xml, char* error,
int error_size) {
mjSpec* ParseSpecFromString(std::string_view xml, char* error, int nerror) {
RegisterResourceProvider();
std::string xml2 = {xml.begin(), xml.end()};
std::string str = "LoadModelFromString:" + xml2;
return mjParseXML(str.c_str(), nullptr, error, error_size);
return ParseXML(str.c_str(), nullptr, error, nerror);
}
// Main writer function - calls mjXWrite
std::string WriteXML(const mjSpec* spec, char* error, int nerror) {
LocaleOverride locale_override;
// check for empty model
if (!spec) {
mjCopyError(error, "Cannot write empty model", nerror);
return "";
}
mjXWriter writer;
writer.SetModel(spec);
return writer.Write(error, nerror);
}
+5 -8
View File
@@ -16,22 +16,19 @@
#define MUJOCO_SRC_XML_XML_H_
#include <string>
#include <string_view>
#include <mujoco/mjexport.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h>
// Top level API
// Main writer function
std::string mjWriteXML(const mjSpec* spec, char* error, int error_sz);
// Main parser function
mjSpec* mjParseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz);
mjSpec* ParseXML(const char* filename, const mjVFS* vfs, char* error, int nerror);
// Returns a newly-allocated mjSpec, loaded from the contents of xml.
// On failure returns nullptr and populates the error array if present.
mjSpec* ParseSpecFromString(std::string_view xml, char* error = nullptr, int error_size = 0);
mjSpec* ParseSpecFromString(std::string_view xml, char* error = nullptr, int nerror = 0);
// Main writer function
std::string WriteXML(const mjSpec* spec, char* error, int nerror);
#endif // MUJOCO_SRC_XML_XML_H_
+9 -5
View File
@@ -37,6 +37,8 @@
//---------------------------------- Globals -------------------------------------------------------
namespace {
// global user model class
class GlobalModel {
public:
@@ -61,7 +63,7 @@ std::optional<std::string> GlobalModel::ToXML(const mjModel* m, char* error,
return std::nullopt;
}
mj_copyBack(spec_, m);
std::string result = mjWriteXML(spec_, error, error_sz);
std::string result = WriteXML(spec_, error, error_sz);
if (result.empty()) {
return std::nullopt;
}
@@ -86,6 +88,8 @@ GlobalModel& GetGlobalModel() {
return global_model;
}
} // namespace
//---------------------------------- Functions -----------------------------------------------------
// parse XML file in MJCF or URDF format, compile it, return low-level model
@@ -96,7 +100,7 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
// parse new model
std::unique_ptr<mjSpec, std::function<void(mjSpec*)>> spec(
mjParseXML(filename, vfs, error, error_sz),
ParseXML(filename, vfs, error, error_sz),
[](mjSpec* s) { mj_deleteSpec(s); });
if (!spec) {
return nullptr;
@@ -211,7 +215,7 @@ mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
// parse spec from file
mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz) {
return mjParseXML(filename, vfs, error, error_sz);
return ParseXML(filename, vfs, error, error_sz);
}
@@ -225,7 +229,7 @@ mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int er
// save spec to XML file, return 1 on success, 0 otherwise
int mj_saveXML(const mjSpec* s, const char* filename, char* error, int error_sz) {
std::string result = mjWriteXML(s, error, error_sz);
std::string result = WriteXML(s, error, error_sz);
if (result.empty()) {
return 0;
}
@@ -241,7 +245,7 @@ int mj_saveXML(const mjSpec* s, const char* filename, char* error, int error_sz)
// save spec to string, return 1 on success, 0 otherwise
int mj_saveXMLString(const mjSpec* s, char* xml, int xml_sz, char* error, int error_sz) {
std::string result = mjWriteXML(s, error, error_sz);
std::string result = WriteXML(s, error, error_sz);
if (result.size() >= xml_sz) {
std::string error_msg = "Output string too short, should be at least " +
std::to_string(result.size()+1);
+33 -24
View File
@@ -49,6 +49,7 @@
namespace {
using std::string;
using std::vector;
using mujoco::user::FilePath;
using tinyxml2::XMLElement;
void ReadPluginConfigs(tinyxml2::XMLElement* elem, mjsPlugin* p) {
@@ -2553,7 +2554,12 @@ void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* pbody) {
ReadAttr(elem, "scale", 3, fcomp.scale, text);
ReadAttr(elem, "mass", 1, &fcomp.mass, text);
ReadAttr(elem, "inertiabox", 1, &fcomp.inertiabox, text);
fcomp.file = ReadAttrFile(elem, "file", modelfiledir_).value_or("");
auto maybe_file = ReadAttrFile(elem, "file", modelfiledir_);
if (maybe_file.has_value()) {
fcomp.file = std::move(maybe_file.value().Str());
} else {
fcomp.file = "";
}
if (ReadAttrTxt(elem, "material", material)) {
mjs_setString(dflex.material, material.c_str());
}
@@ -3171,14 +3177,18 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
}
// separate files
std::vector<string> cubefiles(6);
cubefiles[0] = ReadAttrFile(elem, "fileright", TextureDir()).value_or("");
cubefiles[1] = ReadAttrFile(elem, "fileleft", TextureDir()).value_or("");
cubefiles[2] = ReadAttrFile(elem, "fileup", TextureDir()).value_or("");
cubefiles[3] = ReadAttrFile(elem, "filedown", TextureDir()).value_or("");
cubefiles[4] = ReadAttrFile(elem, "filefront", TextureDir()).value_or("");
cubefiles[5] = ReadAttrFile(elem, "fileback", TextureDir()).value_or("");
std::vector<std::string> cubefiles(6);
std::vector<std::string> cubefile_names = {"fileright", "fileleft",
"fileup", "filedown",
"filefront", "fileback"};
for (int i = 0; i < cubefiles.size(); i++) {
auto maybe_file = ReadAttrFile(elem, cubefile_names[i].c_str(),
TextureDir());
if (maybe_file.has_value()) {
cubefiles[i] = maybe_file.value().Str();
} else {
cubefiles[i] = "";
}
mjs_setInStringVec(ptex->cubefiles, i, cubefiles[i].c_str());
}
}
@@ -3264,7 +3274,7 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
// model sub-element
else if (name=="model") {
auto filename = modelfiledir_ + ReadAttrFile(elem, "file", "").value();
auto filename = modelfiledir_ + ReadAttrFile(elem, "file").value();
// parse the child
std::array<char, 1024> error;
@@ -4227,36 +4237,35 @@ mjsDefault* mjXReader::GetClass(XMLElement* section) {
return def;
}
void mjXReader::SetModelFileDir(std::string modelfiledir) {
modelfiledir_ = modelfiledir;
void mjXReader::SetModelFileDir(const std::string& modelfiledir) {
modelfiledir_ = FilePath(modelfiledir);
}
void mjXReader::SetAssetDir(std::string assetdir) {
assetdir_ = assetdir;
void mjXReader::SetAssetDir(const std::string& assetdir) {
assetdir_ = FilePath(assetdir);
}
void mjXReader::SetMeshDir(std::string meshdir) {
meshdir_ = meshdir;
void mjXReader::SetMeshDir(const std::string& meshdir) {
meshdir_ = FilePath(meshdir);
}
void mjXReader::SetTextureDir(std::string texturedir) {
texturedir_ = texturedir;
void mjXReader::SetTextureDir(const std::string& texturedir) {
texturedir_ = FilePath(texturedir);
}
std::string mjXReader::AssetDir() const {
return mjuu_combinePaths(modelfiledir_, assetdir_);
FilePath mjXReader::AssetDir() const {
return modelfiledir_ + assetdir_;
}
std::string mjXReader::MeshDir() const {
FilePath mjXReader::MeshDir() const {
if (meshdir_.empty()) {
return AssetDir();
}
return mjuu_combinePaths(modelfiledir_, meshdir_);
return modelfiledir_ + meshdir_;
}
std::string mjXReader::TextureDir() const {
FilePath mjXReader::TextureDir() const {
if (texturedir_.empty()) {
return AssetDir();
}
return mjuu_combinePaths(modelfiledir_, texturedir_);
return modelfiledir_ + texturedir_;
}
+15 -14
View File
@@ -22,6 +22,7 @@
#include <mujoco/mujoco.h>
#include <mujoco/mjspec.h>
#include "user/user_util.h"
#include "xml/xml_base.h"
#include "xml/xml_util.h"
@@ -33,13 +34,13 @@ class mjXReader : public mjXBase {
void Parse(tinyxml2::XMLElement* root, const mjVFS* vfs = nullptr); // parse XML document
void PrintSchema(std::stringstream& str, bool html, bool pad); // print text or HTML schema
void SetModelFileDir(std::string modelfiledir);
const std::string& ModelFileDir() const { return modelfiledir_; }
void SetModelFileDir(const std::string& modelfiledir);
const mujoco::user::FilePath& ModelFileDir() const { return modelfiledir_; }
// setters for directory defaults
void SetAssetDir(std::string assetdir);
void SetMeshDir(std::string meshdir);
void SetTextureDir(std::string texturedir);
void SetAssetDir(const std::string& assetdir);
void SetMeshDir(const std::string& meshdir);
void SetTextureDir(const std::string& texturedir);
// XML sections embedded in all formats
static void Compiler(tinyxml2::XMLElement* section, mjSpec* spec); // compiler section
@@ -82,20 +83,20 @@ class mjXReader : public mjXBase {
void OneFlexcomp(tinyxml2::XMLElement* elem, mjsBody* pbody);
void OnePlugin(tinyxml2::XMLElement* elem, mjsPlugin* plugin);
mjXSchema schema; // schema used for validation
mjsDefault* GetClass(tinyxml2::XMLElement* section); // get default class name
mjXSchema schema; // schema used for validation
mjsDefault* GetClass(tinyxml2::XMLElement* section); // get default class name
bool readingdefaults; // true while reading defaults
// accessors for directory defaults
std::string AssetDir() const;
std::string MeshDir() const;
std::string TextureDir() const;
mujoco::user::FilePath AssetDir() const;
mujoco::user::FilePath MeshDir() const;
mujoco::user::FilePath TextureDir() const;
std::string modelfiledir_;
std::string assetdir_;
std::string meshdir_;
std::string texturedir_;
mujoco::user::FilePath modelfiledir_;
mujoco::user::FilePath assetdir_;
mujoco::user::FilePath meshdir_;
mujoco::user::FilePath texturedir_;
};
// MJCF schema
+35 -33
View File
@@ -45,6 +45,7 @@ namespace {
using tinyxml2::XMLAttribute;
using tinyxml2::XMLElement;
using mujoco::user::FilePath;
namespace mju = ::mujoco::util;
@@ -74,6 +75,36 @@ static std::optional<T> ParseInfOrNan(const std::string& s) {
return std::nullopt;
}
FilePath ResolveFilePath(XMLElement* e, const FilePath& filename,
const FilePath& dir) {
std::string path = "";
if (filename.IsAbs()) {
return filename;
}
// TODO(kylebayes): We first look in the base model directory for files to
// remain backwards compatible.
FilePath fullname = dir + filename;
mjResource *resource = mju_openResource(fullname.c_str(), nullptr,
nullptr, 0);
if (resource != nullptr) {
mju_closeResource(resource);
return filename;
}
XMLElement* parent = e->Parent()->ToElement();
for (; parent; parent = parent->Parent()->ToElement()) {
if (!std::strcmp(parent->Value(), "include")) {
auto file_attr = mjXUtil::ReadAttrStr(parent, "dir", false);
if (file_attr.has_value()) {
path = file_attr.value();
}
break;
}
}
return FilePath(path) + filename;
}
} // namespace
@@ -155,36 +186,6 @@ XMLElement* NextSiblingElement(XMLElement* e, const char* name) {
return nullptr;
}
static std::string ResolveFilePath(XMLElement* e, std::string filename,
const std::string& dir) {
std::string path = "";
if (mjuu_isabspath(filename)) {
return filename;
}
// TODO(kylebayes): We first look in the base model directory for files to
// remain backwards compatible.
std::string full_filename = mjuu_combinePaths(dir, filename);
mjResource *resource = mju_openResource(full_filename.c_str(), nullptr,
nullptr, 0);
if (resource != nullptr) {
mju_closeResource(resource);
return filename;
}
XMLElement* parent = e->Parent()->ToElement();
for (; parent; parent = parent->Parent()->ToElement()) {
if (!std::strcmp(parent->Value(), "include")) {
auto file_attr = mjXUtil::ReadAttrStr(parent, "dir", false);
if (file_attr.has_value()) {
path = file_attr.value();
}
break;
}
}
return mjuu_combinePaths(path, filename);
}
// constructor
mjXSchema::mjXSchema(const char* schema[][mjXATTRNUM], unsigned nrow) {
// set name and type
@@ -621,14 +622,15 @@ mjXUtil::ReadAttrStr(XMLElement* elem, const char* attr, bool required) {
}
// if attribute is present, return attribute as a filename
std::optional<std::string>
std::optional<FilePath>
mjXUtil::ReadAttrFile(XMLElement* elem, const char* attr,
const std::string& dir, bool required) {
const FilePath& dir, bool required) {
auto maybe_str = ReadAttrStr(elem, attr, required);
if (!maybe_str.has_value()) {
return std::nullopt;
}
return ResolveFilePath(elem, maybe_str.value(), dir);
FilePath filename(maybe_str.value());
return ResolveFilePath(elem, filename, dir);
}
// if attribute is present, return numerical value of attribute
+13 -6
View File
@@ -26,6 +26,7 @@
#include "tinyxml2.h"
#include "user/user_util.h"
// error string copy
void mjCopyError(char* dst, const char* src, int maxlen);
@@ -101,26 +102,32 @@ class mjXUtil {
// if attribute is present, return vector of numerical data
template<typename T>
static std::optional<std::vector<T>> ReadAttrVec(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<std::vector<T>> ReadAttrVec(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false);
// if attribute is present, return attribute as a string
static std::optional<std::string> ReadAttrStr(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<std::string> ReadAttrStr(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false);
// if attribute is present, return attribute as a filename
static std::optional<std::string> ReadAttrFile(tinyxml2::XMLElement* elem, const char* attr,
const std::string& dir = "", bool required = false);
static std::optional<mujoco::user::FilePath>
ReadAttrFile(tinyxml2::XMLElement* elem, const char* attr,
const mujoco::user::FilePath& dir = mujoco::user::FilePath(),
bool required = false);
// if attribute is present, return numerical value of attribute
template<typename T>
static std::optional<T> ReadAttrNum(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<T> ReadAttrNum(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false);
// if attribute is present, return array of numerical data
// N should be small as data is allocated on the stack
template<typename T, int N>
static std::optional<std::array<T, N>> ReadAttrArr(tinyxml2::XMLElement* elem, const char* attr,
static std::optional<std::array<T, N>> ReadAttrArr(tinyxml2::XMLElement* elem,
const char* attr,
bool required = false) {
std::array<T, N> arr;
int n = 0;
+5
View File
@@ -46,6 +46,11 @@ TEST_F(UserUtilTest, PathReduce2) {
EXPECT_EQ(path.Str(), "../hello/world/");
}
TEST_F(UserUtilTest, PathReduce3) {
FilePath path = FilePath("../../hello/world.txt");
EXPECT_EQ(path.Str(), "../../hello/world.txt");
}
TEST_F(UserUtilTest, PathReduceWin) {
FilePath path = FilePath("C:\\hello\\..\\world");
EXPECT_EQ(path.Str(), "C:\\world");
+14 -10
View File
@@ -649,7 +649,7 @@ TEST_F(XMLReaderTest, IncludeTest) {
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(),
error.size(), vfs.get());
ASSERT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2);
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3);
mj_deleteModel(model);
@@ -733,12 +733,14 @@ TEST_F(XMLReaderTest, IncludePathTest) {
fs.ChangeDirectory("submodels/");
fs.AddFile("model1.xml", (const unsigned char*) xml1, sizeof(xml1));
fs.AddFile("model2.xml", (const unsigned char*) xml2, sizeof(xml2));
fs.AddFile("subsubmodels/model3.xml", (const unsigned char*) xml3, sizeof(xml3));
fs.AddFile("subsubmodels/model3.xml", (const unsigned char*) xml3,
sizeof(xml3));
fs.ChangeDirectory("/");
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
nullptr, 0);
ASSERT_THAT(model, NotNull());
std::array<char, 1024> error;
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error.data(),
error.size());
ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2);
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3);
mj_deleteModel(model);
@@ -920,9 +922,11 @@ TEST_F(XMLReaderTest, IncludeAssetsTest) {
std::string modelpath = fs.FullPath("model.xml");
// loading the file should be successful
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, nullptr, 0);
std::array<char, 1024> error;
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error.data(),
error.size());
EXPECT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
}
@@ -969,7 +973,7 @@ TEST_F(XMLReaderTest, FallbackIncludeAssetsTest) {
std::array<char, 1024> error;
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
error.data(), error.size());
EXPECT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
}
@@ -1013,7 +1017,7 @@ TEST_F(XMLReaderTest, IncludeAbsoluteTest) {
// loading the file should be successful
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
error.data(), error.size());
EXPECT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
}
@@ -1054,7 +1058,7 @@ TEST_F(XMLReaderTest, IncludeAbsoluteMeshDirTest) {
// loading the file should be successful
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
error.data(), error.size());
ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data();
ASSERT_THAT(model, NotNull()) << error.data();
mj_deleteModel(model);
}