Refactor numerical parsing in XML.

PiperOrigin-RevId: 653979240
Change-Id: I4d39f4041b88396193eb35f0cc393141d64f6fc0
This commit is contained in:
Kyle Bayes
2024-07-19 05:40:54 -07:00
committed by Copybara-Service
parent e92af73cbf
commit 8a8a44a4db
13 changed files with 378 additions and 314 deletions
+7 -2
View File
@@ -27,7 +27,12 @@
#include "user/user_model.h" #include "user/user_model.h"
#include "user/user_objects.h" #include "user/user_objects.h"
#include "user/user_cache.h" #include "user/user_cache.h"
#include "xml/xml_util.h"
namespace {
using mujoco::user::StringToVector;
} // namespace
// global cache size in bytes (default 500MB) // global cache size in bytes (default 500MB)
static constexpr std::size_t kGlobalCacheSize = 500 * (1 << 20); static constexpr std::size_t kGlobalCacheSize = 500 * (1 << 20);
@@ -892,7 +897,7 @@ mjtByte mjs_setInStringVec(mjStringVec* dest, int i, const char* text) {
// split text and copy into string array // split text and copy into string array
void mjs_setStringVec(mjStringVec* dest, const char* text) { void mjs_setStringVec(mjStringVec* dest, const char* text) {
std::vector<std::string>* v = static_cast<std::vector<std::string>*>(dest); std::vector<std::string>* v = static_cast<std::vector<std::string>*>(dest);
*v = mjXUtil::String2Vector<std::string>(text); *v = StringToVector<std::string>(text);
} }
+20 -20
View File
@@ -36,12 +36,11 @@
#include "user/user_model.h" #include "user/user_model.h"
#include "user/user_objects.h" #include "user/user_objects.h"
#include "user/user_util.h" #include "user/user_util.h"
#include "xml/xml_util.h"
namespace { namespace {
namespace mju = ::mujoco::util; namespace mju = ::mujoco::util;
using std::vector; using mujoco::user::VectorToString;
using std::string; using mujoco::user::StringToVector;
} // namespace } // namespace
// strncpy with 0, return false // strncpy with 0, return false
@@ -418,12 +417,12 @@ bool mjCComposite::MakeParticle(mjCModel* model, mjsBody* body, char* error, int
} }
} }
} }
mjXUtil::Vector2String(userface, face); userface = VectorToString(face);
} else { } else {
dim = 2; // can only load a surface for now dim = 2; // can only load a surface for now
face = mjXUtil::String2Vector<int>(userface); face = StringToVector<int>(userface);
for (int i=0; i<face.size(); face[i++]--) {}; for (int i=0; i<face.size(); face[i++]--) {};
mjXUtil::Vector2String(userface, face); userface = VectorToString(face);
} }
// compute volume // compute volume
@@ -438,7 +437,7 @@ bool mjCComposite::MakeParticle(mjCModel* model, mjsBody* body, char* error, int
} }
} }
if (!userface.empty()) { if (!userface.empty()) {
face = mjXUtil::String2Vector<int>(userface); face = StringToVector<int>(userface);
for (int j=0; j<face.size()/3; j++) { for (int j=0; j<face.size()/3; j++) {
double area[3]; double area[3];
double edge1[3]; double edge1[3];
@@ -1307,7 +1306,7 @@ void mjCComposite::MakeSkin2(mjCModel* model, mjtNum inflate) {
// copy skin from existing mesh // copy skin from existing mesh
if (type==mjCOMPTYPE_PARTICLE && username.empty()) { if (type==mjCOMPTYPE_PARTICLE && username.empty()) {
std::vector<int> skinface; std::vector<int> skinface;
skinface = mjXUtil::String2Vector<int>(userface); skinface = StringToVector<int>(userface);
int nvert = uservert.size()/3; int nvert = uservert.size()/3;
for (int j=0; j<2; j++) { for (int j=0; j<2; j++) {
@@ -2034,9 +2033,9 @@ void mjCComposite::MakeSkin2Subgrid(mjCModel* model, mjtNum inflate) {
// add skin to 3D // add skin to 3D
void mjCComposite::MakeSkin3(mjCModel* model) { void mjCComposite::MakeSkin3(mjCModel* model) {
int vcnt = 0; int vcnt = 0;
std::map<string, int> vmap; std::map<std::string, int> vmap;
char txt[100], cnt0[10], cnt1[10], cnt2[10]; char txt[100], cnt0[10], cnt1[10], cnt2[10];
string fmt; std::string fmt;
// string counts // string counts
mju::sprintf_arr(cnt0, "%d", count[0]-1); mju::sprintf_arr(cnt0, "%d", count[0]-1);
@@ -2056,17 +2055,17 @@ void mjCComposite::MakeSkin3(mjCModel* model) {
if (type==mjCOMPTYPE_BOX || type==mjCOMPTYPE_PARTICLE) { if (type==mjCOMPTYPE_BOX || type==mjCOMPTYPE_PARTICLE) {
// z-faces // z-faces
MakeSkin3Box(skin, count[0], count[1], 1, vcnt, "%sB%d_%d_0"); MakeSkin3Box(skin, count[0], count[1], 1, vcnt, "%sB%d_%d_0");
fmt = "%sB%d_%d_" + string(cnt2); fmt = "%sB%d_%d_" + std::string(cnt2);
MakeSkin3Box(skin, count[0], count[1], 0, vcnt, fmt.c_str()); MakeSkin3Box(skin, count[0], count[1], 0, vcnt, fmt.c_str());
// y-faces // y-faces
MakeSkin3Box(skin, count[0], count[2], 0, vcnt, "%sB%d_0_%d"); MakeSkin3Box(skin, count[0], count[2], 0, vcnt, "%sB%d_0_%d");
fmt = "%sB%d_" + string(cnt1) + "_%d"; fmt = "%sB%d_" + std::string(cnt1) + "_%d";
MakeSkin3Box(skin, count[0], count[2], 1, vcnt, fmt.c_str()); MakeSkin3Box(skin, count[0], count[2], 1, vcnt, fmt.c_str());
// x-faces // x-faces
MakeSkin3Box(skin, count[1], count[2], 1, vcnt, "%sB0_%d_%d"); MakeSkin3Box(skin, count[1], count[2], 1, vcnt, "%sB0_%d_%d");
fmt = "%sB" + string(cnt0) + "_%d_%d"; fmt = "%sB" + std::string(cnt0) + "_%d_%d";
MakeSkin3Box(skin, count[1], count[2], 0, vcnt, fmt.c_str()); MakeSkin3Box(skin, count[1], count[2], 0, vcnt, fmt.c_str());
} }
@@ -2112,17 +2111,17 @@ void mjCComposite::MakeSkin3(mjCModel* model) {
// y-faces // y-faces
MakeSkin3Smooth(skin, count[0], count[2], 0, vmap, "%sB%d_0_%d"); MakeSkin3Smooth(skin, count[0], count[2], 0, vmap, "%sB%d_0_%d");
fmt = "%sB%d_" + string(cnt1) + "_%d"; fmt = "%sB%d_" + std::string(cnt1) + "_%d";
MakeSkin3Smooth(skin, count[0], count[2], 1, vmap, fmt.c_str()); MakeSkin3Smooth(skin, count[0], count[2], 1, vmap, fmt.c_str());
// x-faces // x-faces
MakeSkin3Smooth(skin, count[1], count[2], 1, vmap, "%sB0_%d_%d"); MakeSkin3Smooth(skin, count[1], count[2], 1, vmap, "%sB0_%d_%d");
fmt = "%sB" + string(cnt0) + "_%d_%d"; fmt = "%sB" + std::string(cnt0) + "_%d_%d";
MakeSkin3Smooth(skin, count[1], count[2], 0, vmap, fmt.c_str()); MakeSkin3Smooth(skin, count[1], count[2], 0, vmap, fmt.c_str());
// z-faces, boxy-type // z-faces, boxy-type
MakeSkin3Box(skin, count[0], count[1], 1, vcnt, "%sB%d_%d_0"); MakeSkin3Box(skin, count[0], count[1], 1, vcnt, "%sB%d_%d_0");
fmt = "%sB%d_%d_" + string(cnt2); fmt = "%sB%d_%d_" + std::string(cnt2);
MakeSkin3Box(skin, count[0], count[1], 0, vcnt, fmt.c_str()); MakeSkin3Box(skin, count[0], count[1], 0, vcnt, fmt.c_str());
} }
@@ -2172,17 +2171,17 @@ void mjCComposite::MakeSkin3(mjCModel* model) {
// z-faces // z-faces
MakeSkin3Smooth(skin, count[0], count[1], 1, vmap, "%sB%d_%d_0"); MakeSkin3Smooth(skin, count[0], count[1], 1, vmap, "%sB%d_%d_0");
fmt = "%sB%d_%d_" + string(cnt2); fmt = "%sB%d_%d_" + std::string(cnt2);
MakeSkin3Smooth(skin, count[0], count[1], 0, vmap, fmt.c_str()); MakeSkin3Smooth(skin, count[0], count[1], 0, vmap, fmt.c_str());
// y-faces // y-faces
MakeSkin3Smooth(skin, count[0], count[2], 0, vmap, "%sB%d_0_%d"); MakeSkin3Smooth(skin, count[0], count[2], 0, vmap, "%sB%d_0_%d");
fmt = "%sB%d_" + string(cnt1) + "_%d"; fmt = "%sB%d_" + std::string(cnt1) + "_%d";
MakeSkin3Smooth(skin, count[0], count[2], 1, vmap, fmt.c_str()); MakeSkin3Smooth(skin, count[0], count[2], 1, vmap, fmt.c_str());
// x-faces // x-faces
MakeSkin3Smooth(skin, count[1], count[2], 1, vmap, "%sB0_%d_%d"); MakeSkin3Smooth(skin, count[1], count[2], 1, vmap, "%sB0_%d_%d");
fmt = "%sB" + string(cnt0) + "_%d_%d"; fmt = "%sB" + std::string(cnt0) + "_%d_%d";
MakeSkin3Smooth(skin, count[1], count[2], 0, vmap, fmt.c_str()); MakeSkin3Smooth(skin, count[1], count[2], 0, vmap, fmt.c_str());
} }
@@ -2248,7 +2247,8 @@ void mjCComposite::MakeSkin3Box(mjsSkin* skin, int c0, int c1, int side,
// make one face of 3D skin, smooth // make one face of 3D skin, smooth
void mjCComposite::MakeSkin3Smooth(mjsSkin* skin, int c0, int c1, int side, void mjCComposite::MakeSkin3Smooth(mjsSkin* skin, int c0, int c1, int side,
const std::map<string, int>& vmap, const char* format) { const std::map<std::string, int>& vmap,
const char* format) {
char txt00[100], txt01[100], txt10[100], txt11[100]; char txt00[100], txt01[100], txt10[100], txt11[100];
// loop over bodies/vertices of specified face // loop over bodies/vertices of specified face
+6 -3
View File
@@ -62,13 +62,16 @@
#include "user/user_objects.h" #include "user/user_objects.h"
#include "user/user_resource.h" #include "user/user_resource.h"
#include "user/user_util.h" #include "user/user_util.h"
#include "xml/xml_util.h"
#include <tiny_obj_loader.h> #include <tiny_obj_loader.h>
extern "C" { extern "C" {
#include "qhull_ra.h" #include "qhull_ra.h"
} }
namespace {
using mujoco::user::VectorToString;
} // namespace
// compute triangle area, surface normal, center // compute triangle area, surface normal, center
static double _triangle(double* normal, double* center, static double _triangle(double* normal, double* center,
const float* v1, const float* v2, const float* v3) { const float* v1, const float* v2, const float* v3) {
@@ -2627,8 +2630,8 @@ void mjCFlex::Compile(const mjVFS* vfs) {
// add plugins // add plugins
std::string userface, useredge; std::string userface, useredge;
mjXUtil::Vector2String(userface, elem_); userface = VectorToString(elem_);
mjXUtil::Vector2String(useredge, edgeidx); useredge = VectorToString(edgeidx);
for (const auto& vbodyid : vertbodyid) { for (const auto& vbodyid : vertbodyid) {
if (model->Bodies()[vbodyid]->plugin.instance) { if (model->Bodies()[vbodyid]->plugin.instance) {
+117 -1
View File
@@ -16,11 +16,13 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <cerrno>
#include <climits> #include <climits>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
@@ -32,6 +34,16 @@
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "engine/engine_crossplatform.h" #include "engine/engine_crossplatform.h"
// workaround with locale bug on some MacOS machines
#if defined (__APPLE__) && defined (__MACH__)
#include <xlocale.h>
#include <locale.h>
#define strtof(X, Y) strtof_l((X), (Y), _c_locale)
#define strtod(X, Y) strtod_l((X), (Y), _c_locale)
#endif
// check if numeric variable is defined // check if numeric variable is defined
bool mjuu_defined(double num) { bool mjuu_defined(double num) {
return !std::isnan(num); return !std::isnan(num);
@@ -1137,5 +1149,109 @@ std::vector<uint8_t> FileToMemory(const char* filename) {
return buffer; return buffer;
} }
} // namespace mujoco::user // convert vector to string separating elements by whitespace
template<typename T> std::string VectorToString(const std::vector<T>& v) {
std::stringstream ss;
for (const T& t : v) {
ss << t << " ";
}
std::string s = ss.str();
if (!s.empty()) s.pop_back(); // remove trailing space
return s;
}
template std::string VectorToString(const std::vector<int>& v);
template std::string VectorToString(const std::vector<float>& v);
template std::string VectorToString(const std::vector<double>& v);
template std::string VectorToString(const std::vector<std::string>& v);
namespace {
template<typename T> T StrToNum(char* str, char** c);
template<> int StrToNum(char* str, char** c) {
long n = std::strtol(str, c, 10);
if (n < INT_MIN || n > INT_MAX) errno = ERANGE;
return n;
}
template<> float StrToNum(char* str, char** c) {
float f = strtof(str, c);
if (std::isnan(f)) errno = EDOM;
return f;
}
template<> double StrToNum(char* str, char** c) {
double d = strtod(str, c);
if (std::isnan(d)) errno = EDOM;
return d;
}
template<> unsigned char StrToNum(char* str, char** c) {
long n = std::strtol(str, c, 10);
if (n < 0 || n > UCHAR_MAX) errno = ERANGE;
return n;
}
inline bool IsNullOrSpace(char* c) {
return std::isspace(static_cast<unsigned char>(*c)) || *c == '\0';
}
inline char* SkipSpace(char* c) {
for (; *c != '\0'; c++) {
if (!IsNullOrSpace(c)) {
break;
}
}
return c;
}
} // namespace
template <typename T> std::vector<T> StringToVector(char* cs) {
std::vector<T> v;
char* ch = cs;
errno = 0;
// reserve worst case
v.reserve((std::strlen(cs) >> 1) + 1);
for (;;) {
cs = SkipSpace(ch); // skip leading spaces
if (*cs == '\0') break; // end of string
T num = StrToNum<T>(cs, &ch); // parse number
if (!IsNullOrSpace(ch)) errno = EINVAL; // invalid separator
if (cs == ch) errno = EINVAL; // failed to parse number
if (errno && errno != EDOM) break; // NaNs are quietly ignored
v.push_back(num);
}
v.shrink_to_fit();
return v;
}
template<> std::vector<std::string> StringToVector(const std::string& s) {
std::vector<std::string> v;
std::stringstream ss(s);
std::string word;
while (ss >> word) {
v.push_back(word);
}
return v;
}
template std::vector<int> StringToVector(char* cs);
template std::vector<float> StringToVector(char* cs);
template std::vector<double> StringToVector(char* cs);
template <typename T> std::vector<T> StringToVector(const std::string& s) {
return StringToVector<T>(const_cast<char*>(s.c_str()));
}
template std::vector<int> StringToVector(const std::string& s);
template std::vector<float> StringToVector(const std::string& s);
template std::vector<double> StringToVector(const std::string& s);
template std::vector<unsigned char> StringToVector(const std::string& s);
} // namespace mujoco::user
+7
View File
@@ -235,6 +235,13 @@ class FilePath {
// read file into memory buffer // read file into memory buffer
std::vector<uint8_t> FileToMemory(const char* filename); std::vector<uint8_t> FileToMemory(const char* filename);
// convert vector to string separating elements by whitespace
template<typename T> std::string VectorToString(const std::vector<T>& v);
// convert string to vector
template<typename T> std::vector<T> StringToVector(char *cs);
template<typename T> std::vector<T> StringToVector(const std::string& s);
} // namespace mujoco::user } // namespace mujoco::user
// strip path from filename // strip path from filename
+51 -46
View File
@@ -1317,17 +1317,17 @@ void mjXReader::OneFlex(XMLElement* elem, mjsFlex* pflex) {
if (ReadAttrTxt(elem, "body", text, true)) { if (ReadAttrTxt(elem, "body", text, true)) {
mjs_setStringVec(pflex->vertbody, text.c_str()); mjs_setStringVec(pflex->vertbody, text.c_str());
} }
if (ReadAttrTxt(elem, "vertex", text)) { auto vert = ReadAttrVec<double>(elem, "vertex");
std::vector<double> vert = String2Vector<double>(text); if (vert.has_value()) {
mjs_setDouble(pflex->vert, vert.data(), vert.size()); mjs_setDouble(pflex->vert, vert->data(), vert->size());
} }
if (ReadAttrTxt(elem, "element", text, true)) { auto element = ReadAttrVec<int>(elem, "element", true);
std::vector<int> elem = String2Vector<int>(text); if (element.has_value()) {
mjs_setInt(pflex->elem, elem.data(), elem.size()); mjs_setInt(pflex->elem, element->data(), element->size());
} }
if (ReadAttrTxt(elem, "texcoord", text)) { auto texcoord = ReadAttrVec<float>(elem, "texcoord");
std::vector<float> texcoord = String2Vector<float>(text); if (texcoord.has_value()) {
mjs_setFloat(pflex->texcoord, texcoord.data(), texcoord.size()); mjs_setFloat(pflex->texcoord, texcoord->data(), texcoord->size());
} }
// contact subelement // contact subelement
@@ -1459,21 +1459,21 @@ void mjXReader::OneSkin(XMLElement* elem, mjsSkin* pskin) {
ReadAttr(elem, "inflate", 1, &pskin->inflate, text); ReadAttr(elem, "inflate", 1, &pskin->inflate, text);
// read vertex data // read vertex data
if (ReadAttrTxt(elem, "vertex", text)) { auto vertex = ReadAttrVec<float>(elem, "vertex");
std::vector<float> vert = String2Vector<float>(text); if (vertex.has_value()) {
mjs_setFloat(pskin->vert, vert.data(), vert.size()); mjs_setFloat(pskin->vert, vertex->data(), vertex->size());
} }
// read texcoord data // read texcoord data
if (ReadAttrTxt(elem, "texcoord", text)) { auto texcoord = ReadAttrVec<float>(elem, "texcoord");
std::vector<float> texcoord = String2Vector<float>(text); if (texcoord.has_value()) {
mjs_setFloat(pskin->texcoord, texcoord.data(), texcoord.size()); mjs_setFloat(pskin->texcoord, texcoord->data(), texcoord->size());
} }
// read user face data // read user face data
if (ReadAttrTxt(elem, "face", text)) { auto face = ReadAttrVec<int>(elem, "face");
std::vector<int> face = String2Vector<int>(text); if (face.has_value()) {
mjs_setInt(pskin->face, face.data(), face.size()); mjs_setInt(pskin->face, face->data(), face->size());
} }
// read bones // read bones
@@ -1500,14 +1500,16 @@ void mjXReader::OneSkin(XMLElement* elem, mjsSkin* pskin) {
bindquat.push_back(data[3]); bindquat.push_back(data[3]);
// read vertid // read vertid
ReadAttrTxt(bone, "vertid", text, true); auto tempid = ReadAttrVec<int>(bone, "vertid", true);
vector<int> tempid = String2Vector<int>(text); if (tempid.has_value()) {
mjs_appendIntVec(pskin->vertid, tempid.data(), tempid.size()); mjs_appendIntVec(pskin->vertid, tempid->data(), tempid->size());
}
// read vertweight // read vertweight
ReadAttrTxt(bone, "vertweight", text, true); auto tempweight = ReadAttrVec<float>(bone, "vertweight", true);
vector<float> tempweight = String2Vector<float>(text); if (tempweight.has_value()) {
mjs_appendFloatVec(pskin->vertweight, tempweight.data(), tempweight.size()); mjs_appendFloatVec(pskin->vertweight, tempweight->data(), tempweight->size());
}
// advance to next bone // advance to next bone
bone = NextSiblingElement(bone, "bone"); bone = NextSiblingElement(bone, "bone");
@@ -2287,8 +2289,9 @@ void mjXReader::OneComposite(XMLElement* elem, mjsBody* pbody, mjsDefault* def)
ReadAttrTxt(elem, "curve", curves); ReadAttrTxt(elem, "curve", curves);
ReadAttrTxt(elem, "initial", comp.initial); ReadAttrTxt(elem, "initial", comp.initial);
ReadAttr(elem, "size", 3, comp.size, text, false, false); ReadAttr(elem, "size", 3, comp.size, text, false, false);
if (ReadAttrTxt(elem, "vertex", text)) { auto uservert = ReadAttrVec<float>(elem, "vertex");
comp.uservert = String2Vector<float>(text); if (uservert.has_value()) {
comp.uservert = std::move(uservert.value());
} }
// shell // shell
@@ -2530,14 +2533,17 @@ void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* pbody) {
if (MapValue(elem, "rigid", &n, bool_map, 2)) { if (MapValue(elem, "rigid", &n, bool_map, 2)) {
fcomp.rigid = (n==1); fcomp.rigid = (n==1);
} }
if (ReadAttrTxt(elem, "point", text)){ auto point = ReadAttrVec<double>(elem, "point");
fcomp.point = String2Vector<double>(text); if (point.has_value()) {
fcomp.point = std::move(point.value());
} }
if (ReadAttrTxt(elem, "element", text)){ auto element = ReadAttrVec<int>(elem, "element");
fcomp.element = String2Vector<int>(text); if (element.has_value()) {
fcomp.element = std::move(element.value());
} }
if (ReadAttrTxt(elem, "texcoord", text)) { auto texcoord = ReadAttrVec<float>(elem, "texcoord");
fcomp.texcoord = String2Vector<float>(text); if (texcoord.has_value()) {
fcomp.texcoord = std::move(texcoord.value());
} }
// edge // edge
@@ -2575,23 +2581,22 @@ void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* pbody) {
// pin // pin
XMLElement* epin = FirstChildElement(elem, "pin"); XMLElement* epin = FirstChildElement(elem, "pin");
while (epin) { while (epin) {
// accumulate id, coord, range auto id = ReadAttrVec<int>(epin, "id");
if (ReadAttrTxt(epin, "id", text)) { if (id.has_value()) {
vector<int> v = String2Vector<int>(text); fcomp.pinid.insert(fcomp.pinid.end(), id->begin(), id->end());
fcomp.pinid.insert(fcomp.pinid.end(), v.begin(), v.end());
} }
if (ReadAttrTxt(epin, "range", text)) { auto range = ReadAttrVec<int>(epin, "range");
vector<int> v = String2Vector<int>(text); if (range.has_value()) {
fcomp.pinrange.insert(fcomp.pinrange.end(), v.begin(), v.end()); fcomp.pinrange.insert(fcomp.pinrange.end(), range->begin(), range->end());
} }
if (ReadAttrTxt(epin, "grid", text)) { auto grid = ReadAttrVec<int>(epin, "grid");
if (grid.has_value()) {
vector<int> v = String2Vector<int>(text); fcomp.pingrid.insert(fcomp.pingrid.end(), grid->begin(), grid->end());
fcomp.pingrid.insert(fcomp.pingrid.end(), v.begin(), v.end());
} }
if (ReadAttrTxt(epin, "gridrange", text)) { auto gridrange = ReadAttrVec<int>(epin, "gridrange");
vector<int> v = String2Vector<int>(text); if (gridrange.has_value()) {
fcomp.pingridrange.insert(fcomp.pingridrange.end(), v.begin(), v.end()); fcomp.pingridrange.insert(fcomp.pingridrange.end(),
gridrange->begin(), gridrange->end());
} }
// advance // advance
+14 -13
View File
@@ -44,6 +44,7 @@ using std::string;
using tinyxml2::XMLComment; using tinyxml2::XMLComment;
using tinyxml2::XMLDocument; using tinyxml2::XMLDocument;
using tinyxml2::XMLElement; using tinyxml2::XMLElement;
using mujoco::user::VectorToString;
} // namespace } // namespace
@@ -142,19 +143,19 @@ void mjXWriter::OneFlex(XMLElement* elem, const mjCFlex* pflex) {
// data vectors // data vectors
if (!pflex->get_vertbody().empty()) { if (!pflex->get_vertbody().empty()) {
Vector2String(text, pflex->get_vertbody()); text = VectorToString(pflex->get_vertbody());
WriteAttrTxt(elem, "body", text); WriteAttrTxt(elem, "body", text);
} }
if (!pflex->get_vert().empty()) { if (!pflex->get_vert().empty()) {
Vector2String(text, pflex->get_vert()); text = VectorToString(pflex->get_vert());
WriteAttrTxt(elem, "vertex", text); WriteAttrTxt(elem, "vertex", text);
} }
if (!pflex->get_elem().empty()) { if (!pflex->get_elem().empty()) {
Vector2String(text, pflex->get_elem()); text = VectorToString(pflex->get_elem());
WriteAttrTxt(elem, "element", text); WriteAttrTxt(elem, "element", text);
} }
if (!pflex->get_texcoord().empty()) { if (!pflex->get_texcoord().empty()) {
Vector2String(text, pflex->get_texcoord()); text = VectorToString(pflex->get_texcoord());
WriteAttrTxt(elem, "texcoord", text); WriteAttrTxt(elem, "texcoord", text);
} }
@@ -207,25 +208,25 @@ void mjXWriter::OneMesh(XMLElement* elem, const mjCMesh* pmesh, mjCDef* def) {
// write vertex data // write vertex data
if (!pmesh->get_uservert().empty()) { if (!pmesh->get_uservert().empty()) {
Vector2String(text, pmesh->get_uservert()); text = VectorToString(pmesh->get_uservert());
WriteAttrTxt(elem, "vertex", text); WriteAttrTxt(elem, "vertex", text);
} }
// write normal data // write normal data
if (!pmesh->get_usernormal().empty()) { if (!pmesh->get_usernormal().empty()) {
Vector2String(text, pmesh->get_usernormal()); text = VectorToString(pmesh->get_usernormal());
WriteAttrTxt(elem, "normal", text); WriteAttrTxt(elem, "normal", text);
} }
// write texcoord data // write texcoord data
if (!pmesh->get_usertexcoord().empty()) { if (!pmesh->get_usertexcoord().empty()) {
Vector2String(text, pmesh->get_usertexcoord()); text = VectorToString(pmesh->get_usertexcoord());
WriteAttrTxt(elem, "texcoord", text); WriteAttrTxt(elem, "texcoord", text);
} }
// write face data // write face data
if (!pmesh->get_userface().empty()) { if (!pmesh->get_userface().empty()) {
Vector2String(text, pmesh->get_userface()); text = VectorToString(pmesh->get_userface());
WriteAttrTxt(elem, "face", text); WriteAttrTxt(elem, "face", text);
} }
} }
@@ -257,17 +258,17 @@ void mjXWriter::OneSkin(XMLElement* elem, const mjCSkin* pskin) {
// write data if no file // write data if no file
if (pskin->get_file().empty()) { if (pskin->get_file().empty()) {
// mesh vert // mesh vert
Vector2String(text, pskin->get_vert()); text = VectorToString(pskin->get_vert());
WriteAttrTxt(elem, "vertex", text); WriteAttrTxt(elem, "vertex", text);
// mesh texcoord // mesh texcoord
if (!pskin->get_texcoord().empty()) { if (!pskin->get_texcoord().empty()) {
Vector2String(text, pskin->get_texcoord()); text = VectorToString(pskin->get_texcoord());
WriteAttrTxt(elem, "texcoord", text); WriteAttrTxt(elem, "texcoord", text);
} }
// mesh face // mesh face
Vector2String(text, pskin->get_face()); text = VectorToString(pskin->get_face());
WriteAttrTxt(elem, "face", text); WriteAttrTxt(elem, "face", text);
// bones // bones
@@ -281,11 +282,11 @@ void mjXWriter::OneSkin(XMLElement* elem, const mjCSkin* pskin) {
WriteAttr(bone, "bindquat", 4, pskin->get_bindquat().data()+4*i); WriteAttr(bone, "bindquat", 4, pskin->get_bindquat().data()+4*i);
// write vertid // write vertid
Vector2String(text, pskin->get_vertid()[i]); text = VectorToString(pskin->get_vertid()[i]);
WriteAttrTxt(bone, "vertid", text); WriteAttrTxt(bone, "vertid", text);
// write vertweight // write vertweight
Vector2String(text, pskin->get_vertweight()[i]); text = VectorToString(pskin->get_vertweight()[i]);
WriteAttrTxt(bone, "vertweight", text); WriteAttrTxt(bone, "vertweight", text);
} }
} }
+47 -138
View File
@@ -43,10 +43,6 @@
namespace { namespace {
using std::size_t;
using std::string;
using std::stringstream;
using std::vector;
using tinyxml2::XMLAttribute; using tinyxml2::XMLAttribute;
using tinyxml2::XMLElement; using tinyxml2::XMLElement;
@@ -238,7 +234,7 @@ mjXSchema::mjXSchema(const char* schema[][mjXATTRNUM], unsigned nrow) {
// get pointer to error message // get pointer to error message
string mjXSchema::GetError() { std::string mjXSchema::GetError() {
return error; return error;
} }
@@ -256,7 +252,7 @@ static void printspace(std::stringstream& str, int n, const char* space) {
// print schema as text // print schema as text
void mjXSchema::Print(std::stringstream& str, int level) const { void mjXSchema::Print(std::stringstream& str, int level) const {
// replace body with (world)body // replace body with (world)body
string name1 = (name_ == "body") ? "(world)body" : name_; std::string name1 = (name_ == "body") ? "(world)body" : name_;
// space, name, type // space, name, type
printspace(str, 3*level, " "); printspace(str, 3*level, " ");
@@ -290,7 +286,7 @@ void mjXSchema::Print(std::stringstream& str, int level) const {
// print schema as HTML table // print schema as HTML table
void mjXSchema::PrintHTML(std::stringstream& str, int level, bool pad) const { void mjXSchema::PrintHTML(std::stringstream& str, int level, bool pad) const {
// replace body with (world)body // replace body with (world)body
string name1 = (name_ == "body" ? "(world)body" : name_); std::string name1 = (name_ == "body" ? "(world)body" : name_);
// open table // open table
if (level==0) { if (level==0) {
@@ -377,7 +373,7 @@ XMLElement* mjXSchema::Check(XMLElement* elem, int level) {
const XMLAttribute* attribute = elem->FirstAttribute(); const XMLAttribute* attribute = elem->FirstAttribute();
for (; attribute != nullptr; attribute = attribute->Next()) { for (; attribute != nullptr; attribute = attribute->Next()) {
if (attr_.find(attribute->Name()) == attr_.end()) { if (attr_.find(attribute->Name()) == attr_.end()) {
error = "unrecognized attribute: '" + string(attribute->Name()) + "'"; error = "unrecognized attribute: '" + std::string(attribute->Name()) + "'";
return elem; return elem;
} }
} }
@@ -541,7 +537,7 @@ template bool mjXUtil::SameVector(const unsigned char* vec1, const unsigned char
// find string in map, return corresponding integer (-1: not found) // find string in map, return corresponding integer (-1: not found)
int mjXUtil::FindKey(const mjMap* map, int mapsz, string key) { int mjXUtil::FindKey(const mjMap* map, int mapsz, std::string key) {
for (int i=0; i<mapsz; i++) { for (int i=0; i<mapsz; i++) {
if (map[i].key == key) { if (map[i].key == key) {
return map[i].value; return map[i].value;
@@ -554,7 +550,7 @@ int mjXUtil::FindKey(const mjMap* map, int mapsz, string key) {
// find integer in map, return corresponding string ("": not found) // find integer in map, return corresponding string ("": not found)
string mjXUtil::FindValue(const mjMap* map, int mapsz, int value) { std::string mjXUtil::FindValue(const mjMap* map, int mapsz, int value) {
for (int i=0; i<mapsz; i++) { for (int i=0; i<mapsz; i++) {
if (map[i].value == value) { if (map[i].value == value) {
return map[i].key; return map[i].key;
@@ -570,9 +566,22 @@ string mjXUtil::FindValue(const mjMap* map, int mapsz, int value) {
template<typename T> template<typename T>
std::optional<std::vector<T>> mjXUtil::ReadAttrVec(XMLElement* elem, const char* attr, std::optional<std::vector<T>> mjXUtil::ReadAttrVec(XMLElement* elem, const char* attr,
bool required) { bool required) {
std::vector<T> vec; std::vector<T> v;
ReadAttrValues<T>(elem, attr, [&](int i, T num) { vec.push_back(num); }); const char* raw_cstr = elem->Attribute(attr);
if (!vec.size()) { if (raw_cstr) {
v = mujoco::user::StringToVector<T>(raw_cstr);
if (errno == EDOM) {
mju_warning("XML contains a 'NaN'. Please check it carefully.");
} else if (errno == ERANGE) {
throw mjXError(elem, "number is too large in attribute '%s'", attr);
} else if (errno == EINVAL) {
throw mjXError(elem, "bad format in attribute '%s'", attr);
} else if (errno != 0) {
throw mjXError(elem, "unknown error in attribute '%s'", attr);
}
}
if (!v.size()) {
if (required) { if (required) {
throw mjXError(elem, "required attribute missing: '%s'", attr); throw mjXError(elem, "required attribute missing: '%s'", attr);
} else { } else {
@@ -580,7 +589,7 @@ std::optional<std::vector<T>> mjXUtil::ReadAttrVec(XMLElement* elem, const char*
} }
} }
return vec; return v;
} }
template std::optional<std::vector<double>> template std::optional<std::vector<double>>
@@ -651,7 +660,7 @@ mjXUtil::ReadAttrNum(XMLElement* elem, const char* attr, bool required);
// return number of elements found // return number of elements found
template<typename T> template<typename T>
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len, int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
T* data, string& text, bool required, bool exact) { T* data, std::string& text, bool required, bool exact) {
auto maybe_vec = ReadAttrVec<T>(elem, attr, required); auto maybe_vec = ReadAttrVec<T>(elem, attr, required);
if (!maybe_vec.has_value()) { if (!maybe_vec.has_value()) {
return 0; return 0;
@@ -672,21 +681,21 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
} }
template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len, template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len,
double* data, string& text, bool required, bool exact); double* data, std::string& text, bool required, bool exact);
template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len, template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len,
float* data, string& text, bool required, bool exact); float* data, std::string& text, bool required, bool exact);
template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len, template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len,
int* data, string& text, bool required, bool exact); int* data, std::string& text, bool required, bool exact);
template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len, template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, int len,
unsigned char* data, string& text, bool required, unsigned char* data, std::string& text, bool required,
bool exact); bool exact);
// read quaternion attribute // read quaternion attribute
// throw error if identically zero // throw error if identically zero
int mjXUtil::ReadQuat(XMLElement* elem, const char* attr, double* data, string& text, int mjXUtil::ReadQuat(XMLElement* elem, const char* attr, double* data, std::string& text,
bool required) { bool required) {
ReadAttr(elem, attr, /*len=*/4, data, text, required, /*exact=*/true); ReadAttr(elem, attr, /*len=*/4, data, text, required, /*exact=*/true);
@@ -700,7 +709,7 @@ int mjXUtil::ReadQuat(XMLElement* elem, const char* attr, double* data, string&
// read DOUBLE array into C++ vector, return number read // read DOUBLE array into C++ vector, return number read
int mjXUtil::ReadVector(XMLElement* elem, const char* attr, int mjXUtil::ReadVector(XMLElement* elem, const char* attr,
vector<double>& vec, string& text, bool required) { std::vector<double>& vec, std::string& text, bool required) {
auto maybe_vec = ReadAttrVec<double>(elem, attr, required); auto maybe_vec = ReadAttrVec<double>(elem, attr, required);
if (!maybe_vec.has_value()) { if (!maybe_vec.has_value()) {
return 0; return 0;
@@ -713,7 +722,8 @@ int mjXUtil::ReadVector(XMLElement* elem, const char* attr,
// read text field // read text field
bool mjXUtil::ReadAttrTxt(tinyxml2::XMLElement* elem, const char* attr, string& text, bool required) { bool mjXUtil::ReadAttrTxt(tinyxml2::XMLElement* elem, const char* attr,
std::string& text, bool required) {
auto maybe_str = ReadAttrStr(elem, attr, required); auto maybe_str = ReadAttrStr(elem, attr, required);
if (!maybe_str.has_value()) { if (!maybe_str.has_value()) {
return false; return false;
@@ -735,92 +745,9 @@ bool mjXUtil::ReadAttrInt(XMLElement* elem, const char* attr, int* data, bool re
} }
template<> int mjXUtil::StrToNum(char* str, char** c) {
return std::strtol(str, c, 10);
}
template<> float mjXUtil::StrToNum(char* str, char** c) {
return std::strtof(str, c);
}
template<> double mjXUtil::StrToNum(char* str, char** c) {
return std::strtod(str, c);
}
template <typename T>
std::vector<T> mjXUtil::String2Vector(const std::string& s) {
errno = 0;
std::vector<T> v;
char* cs = (char*) s.c_str();
char* ch = cs;
// reserve worst case
v.reserve((s.size() >> 1) + 1);
for (;;) {
cs = ch;
T num = StrToNum<T>(cs, &ch);
if (cs == ch) break;
if (errno) break;
v.push_back(num);
}
v.shrink_to_fit();
return v;
}
template std::vector<int> mjXUtil::String2Vector(const std::string& s);
template std::vector<float> mjXUtil::String2Vector(const std::string& s);
template std::vector<double> mjXUtil::String2Vector(const std::string& s);
template<>
std::vector<std::string> mjXUtil::String2Vector(const std::string& s) {
std::vector<std::string> v;
std::stringstream ss(s);
std::string word;
while (ss >> word) {
v.push_back(word);
}
return v;
}
// write vector<float> to string // write vector<float> to string
void mjXUtil::Vector2String(string& txt, const vector<string>& vec) { void mjXUtil::Vector2String(std::string& txt, const std::vector<float>& vec, int ncol) {
stringstream strm; std::stringstream strm;
for (size_t i=0; i<vec.size(); i++) {
if (i>0) {
strm << " ";
}
strm << vec[i];
}
txt = strm.str();
}
// write vector<double> to string
void mjXUtil::Vector2String(string& txt, const vector<double>& vec) {
stringstream strm;
for (size_t i=0; i<vec.size(); i++) {
if (i>0) {
strm << " ";
}
strm << vec[i];
}
txt = strm.str();
}
// write vector<float> to string
void mjXUtil::Vector2String(string& txt, const vector<float>& vec, int ncol) {
stringstream strm;
for (size_t i=0; i<vec.size(); i++) { for (size_t i=0; i<vec.size(); i++) {
if (ncol && (i % ncol) == 0) { if (ncol && (i % ncol) == 0) {
@@ -834,26 +761,8 @@ void mjXUtil::Vector2String(string& txt, const vector<float>& vec, int ncol) {
txt = strm.str(); txt = strm.str();
} }
// write vector<int> to string
void mjXUtil::Vector2String(string& txt, const vector<int>& vec) {
stringstream strm;
for (size_t i=0; i<vec.size(); i++) {
if (i>0) {
strm << " ";
}
strm << vec[i];
}
txt = strm.str();
}
// find subelement with given name, make sure it is unique // find subelement with given name, make sure it is unique
XMLElement* mjXUtil::FindSubElem(XMLElement* elem, string name, bool required) { XMLElement* mjXUtil::FindSubElem(XMLElement* elem, std::string name, bool required) {
XMLElement* subelem = 0; XMLElement* subelem = 0;
XMLElement* iter = elem->FirstChildElement(); XMLElement* iter = elem->FirstChildElement();
@@ -924,7 +833,7 @@ static int Round(double x) {
// write attribute // write attribute
template<typename T> template<typename T>
void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const T* data, const T* def, void mjXUtil::WriteAttr(XMLElement* elem, std::string name, int n, const T* data, const T* def,
bool trim) { bool trim) {
// make sure all are defined // make sure all are defined
if constexpr (std::is_floating_point_v<T>) { if constexpr (std::is_floating_point_v<T>) {
@@ -948,7 +857,7 @@ void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const T* data, con
} }
// increase precision for testing // increase precision for testing
stringstream stream; std::stringstream stream;
stream.precision(mujoco::_mjPRIVATE__get_xml_precision()); stream.precision(mujoco::_mjPRIVATE__get_xml_precision());
// process all numbers // process all numbers
@@ -972,22 +881,22 @@ void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const T* data, con
} }
template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, template void mjXUtil::WriteAttr(XMLElement* elem, std::string name, int n,
const double* data, const double* def, bool trim); const double* data, const double* def, bool trim);
template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, template void mjXUtil::WriteAttr(XMLElement* elem, std::string name, int n,
const float* data, const float* def, bool trim); const float* data, const float* def, bool trim);
template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, template void mjXUtil::WriteAttr(XMLElement* elem, std::string name, int n,
const int* data, const int* def, bool trim); const int* data, const int* def, bool trim);
template void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, template void mjXUtil::WriteAttr(XMLElement* elem, std::string name, int n,
const unsigned char* data, const unsigned char* data,
const unsigned char* def, bool trim); const unsigned char* def, bool trim);
// write vector<double> attribute, default = zero array // write vector<double> attribute, default = zero array
void mjXUtil::WriteVector(XMLElement* elem, string name, const vector<double>& vec) { void mjXUtil::WriteVector(XMLElement* elem, std::string name, const std::vector<double>& vec) {
// proceed only if non-zero found // proceed only if non-zero found
bool ok = false; bool ok = false;
for (size_t i=0; i<vec.size(); i++) { for (size_t i=0; i<vec.size(); i++) {
@@ -1006,8 +915,8 @@ void mjXUtil::WriteVector(XMLElement* elem, string name, const vector<double>& v
// write vector<double> attribute, default with same size // write vector<double> attribute, default with same size
void mjXUtil::WriteVector(XMLElement* elem, string name, const vector<double>& vec, void mjXUtil::WriteVector(XMLElement* elem, std::string name, const std::vector<double>& vec,
const vector<double>& def) { const std::vector<double>& def) {
// proceed only if non-zero found // proceed only if non-zero found
bool ok = false; bool ok = false;
for (size_t i=0; i<vec.size(); i++) { for (size_t i=0; i<vec.size(); i++) {
@@ -1026,7 +935,7 @@ void mjXUtil::WriteVector(XMLElement* elem, string name, const vector<double>& v
// write attribute- string // write attribute- string
void mjXUtil::WriteAttrTxt(XMLElement* elem, string name, string value) { void mjXUtil::WriteAttrTxt(XMLElement* elem, std::string name, std::string value) {
// skip if value is empty // skip if value is empty
if (value.empty()) { if (value.empty()) {
return; return;
@@ -1039,7 +948,7 @@ void mjXUtil::WriteAttrTxt(XMLElement* elem, string name, string value) {
// write attribute- single int // write attribute- single int
void mjXUtil::WriteAttrInt(XMLElement* elem, string name, int data, int def) { void mjXUtil::WriteAttrInt(XMLElement* elem, std::string name, int data, int def) {
// skip default // skip default
if (data==def) { if (data==def) {
return; return;
@@ -1051,7 +960,7 @@ void mjXUtil::WriteAttrInt(XMLElement* elem, string name, int data, int def) {
// write attribute- keyword // write attribute- keyword
void mjXUtil::WriteAttrKey(XMLElement* elem, string name, void mjXUtil::WriteAttrKey(XMLElement* elem, std::string name,
const mjMap* map, int mapsz, int data, int def) { const mjMap* map, int mapsz, int data, int def) {
// skip default // skip default
if (data==def) { if (data==def) {
-14
View File
@@ -163,21 +163,9 @@ class mjXUtil {
static bool ReadAttrInt(tinyxml2::XMLElement* elem, const char* attr, int* data, static bool ReadAttrInt(tinyxml2::XMLElement* elem, const char* attr, int* data,
bool required = false); bool required = false);
// convert string to vector
template<typename T>
static std::vector<T> String2Vector(const std::string& s);
// write vector<string> to string
static void Vector2String(std::string& txt, const std::vector<std::string>& vec);
// write vector<double> to string
static void Vector2String(std::string& txt, const std::vector<double>& vec);
// write vector<float> to string // write vector<float> to string
static void Vector2String(std::string& txt, const std::vector<float>& vec, int ncol = 0); static void Vector2String(std::string& txt, const std::vector<float>& vec, int ncol = 0);
// write vector<int> to string
static void Vector2String(std::string& txt, const std::vector<int>& vec);
// find subelement with given name, make sure it is unique // find subelement with given name, make sure it is unique
static tinyxml2::XMLElement* FindSubElem(tinyxml2::XMLElement* elem, std::string name, static tinyxml2::XMLElement* FindSubElem(tinyxml2::XMLElement* elem, std::string name,
@@ -212,8 +200,6 @@ class mjXUtil {
template<typename T> template<typename T>
static bool ReadAttrValues(tinyxml2::XMLElement* elem, const char* attr, static bool ReadAttrValues(tinyxml2::XMLElement* elem, const char* attr,
std::function<void (int, T)> push, int max = -1); std::function<void (int, T)> push, int max = -1);
template<typename T> static T StrToNum(char* str, char** c);
}; };
#endif // MUJOCO_SRC_XML_XML_UTIL_H_ #endif // MUJOCO_SRC_XML_XML_UTIL_H_
+86 -15
View File
@@ -16,93 +16,164 @@
#include "src/user/user_util.h" #include "src/user/user_util.h"
#include <cerrno>
#include <string>
#include <vector>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <mujoco/mujoco.h>
#include "test/fixture.h"
namespace mujoco { namespace mujoco {
namespace { namespace {
using user::FilePath; using user::FilePath;
using user::StringToVector;
using user::VectorToString;
using ::testing::ElementsAre;
using ::testing::IsNan;
TEST(UserUtilTest, PathReduce) { using UserUtilTest = MujocoTest;
TEST_F(UserUtilTest, PathReduce) {
FilePath path = FilePath("/hello/.././world/"); FilePath path = FilePath("/hello/.././world/");
EXPECT_EQ(path.Str(), "/world/"); EXPECT_EQ(path.Str(), "/world/");
} }
TEST(UserUtilTest, PathReduce2) { TEST_F(UserUtilTest, PathReduce2) {
FilePath path = FilePath("../hello/./world/"); FilePath path = FilePath("../hello/./world/");
EXPECT_EQ(path.Str(), "../hello/world/"); EXPECT_EQ(path.Str(), "../hello/world/");
} }
TEST(UserUtilTest, PathReduceWin) { TEST_F(UserUtilTest, PathReduceWin) {
FilePath path = FilePath("C:\\hello\\..\\world"); FilePath path = FilePath("C:\\hello\\..\\world");
EXPECT_EQ(path.Str(), "C:\\world"); EXPECT_EQ(path.Str(), "C:\\world");
} }
TEST(UserUtilTest, IsAbs) { TEST_F(UserUtilTest, IsAbs) {
EXPECT_TRUE(FilePath("/hello").IsAbs()); EXPECT_TRUE(FilePath("/hello").IsAbs());
EXPECT_TRUE(FilePath("C:\\hello").IsAbs()); EXPECT_TRUE(FilePath("C:\\hello").IsAbs());
EXPECT_FALSE(FilePath("hello").IsAbs()); EXPECT_FALSE(FilePath("hello").IsAbs());
} }
TEST(UserUtilTest, Combine) { TEST_F(UserUtilTest, Combine) {
FilePath path1 = FilePath("/hello"); FilePath path1 = FilePath("/hello");
FilePath path2 = FilePath("world"); FilePath path2 = FilePath("world");
EXPECT_EQ((path1 + path2).Str(), "/hello/world"); EXPECT_EQ((path1 + path2).Str(), "/hello/world");
} }
TEST(UserUtilTest, Combine2) { TEST_F(UserUtilTest, Combine2) {
FilePath path1 = FilePath("hello/"); FilePath path1 = FilePath("hello/");
FilePath path2 = FilePath("world"); FilePath path2 = FilePath("world");
EXPECT_EQ((path1 + path2).Str(), "hello/world"); EXPECT_EQ((path1 + path2).Str(), "hello/world");
} }
TEST(UserUtilTest, Combine3) { TEST_F(UserUtilTest, Combine3) {
FilePath path1 = FilePath("/hello"); FilePath path1 = FilePath("/hello");
FilePath path2 = FilePath("../world"); FilePath path2 = FilePath("../world");
EXPECT_EQ((path1 + path2).Str(), "/world"); EXPECT_EQ((path1 + path2).Str(), "/world");
} }
TEST(UserUtilTest, CombineAbs) { TEST_F(UserUtilTest, CombineAbs) {
FilePath path1 = FilePath("/hello"); FilePath path1 = FilePath("/hello");
FilePath path2 = FilePath("/world"); FilePath path2 = FilePath("/world");
EXPECT_EQ((path1 + path2).Str(), "/world"); EXPECT_EQ((path1 + path2).Str(), "/world");
} }
TEST(UserUtilTest, Ext) { TEST_F(UserUtilTest, Ext) {
FilePath path = FilePath("/hello/world.txt"); FilePath path = FilePath("/hello/world.txt");
EXPECT_EQ(path.Ext(), ".txt"); EXPECT_EQ(path.Ext(), ".txt");
} }
TEST(UserUtilTest, ExtEmpty) { TEST_F(UserUtilTest, ExtEmpty) {
FilePath path = FilePath("/hello/world"); FilePath path = FilePath("/hello/world");
EXPECT_EQ(path.Ext(), ""); EXPECT_EQ(path.Ext(), "");
} }
TEST(UserUtilTest, StripExt) { TEST_F(UserUtilTest, StripExt) {
FilePath path = FilePath("/hello/world.txt"); FilePath path = FilePath("/hello/world.txt");
EXPECT_EQ(path.StripExt().Str(), "/hello/world"); EXPECT_EQ(path.StripExt().Str(), "/hello/world");
} }
TEST(UserUtilTest, StripPath) { TEST_F(UserUtilTest, StripPath) {
FilePath path = FilePath("/hello/world.txt"); FilePath path = FilePath("/hello/world.txt");
EXPECT_EQ(path.StripPath().Str(), "world.txt"); EXPECT_EQ(path.StripPath().Str(), "world.txt");
} }
TEST(UserUtilTest, StripPathEmpty) { TEST_F(UserUtilTest, StripPathEmpty) {
FilePath path = FilePath("world.txt"); FilePath path = FilePath("world.txt");
EXPECT_EQ(path.StripPath().Str(), "world.txt"); EXPECT_EQ(path.StripPath().Str(), "world.txt");
} }
TEST(UserUtilTest, StripPathWin) { TEST_F(UserUtilTest, StripPathWin) {
FilePath path = FilePath("\\world.txt"); FilePath path = FilePath("\\world.txt");
EXPECT_EQ(path.StripPath().Str(), "world.txt"); EXPECT_EQ(path.StripPath().Str(), "world.txt");
} }
TEST(UserUtilTest, StrLower) { TEST_F(UserUtilTest, StrLower) {
FilePath path = FilePath("/HELLO/worlD.txt"); FilePath path = FilePath("/HELLO/worlD.txt");
EXPECT_EQ(path.StrLower(), "/hello/world.txt"); EXPECT_EQ(path.StrLower(), "/hello/world.txt");
} }
TEST_F(UserUtilTest, StringToVectorFloat) {
std::vector<float> v = StringToVector<float>(" 1.2 3.2 5.3 6 ");
EXPECT_THAT(v, ElementsAre(1.2, 3.2, 5.3, 6));
EXPECT_EQ(errno, 0);
}
TEST_F(UserUtilTest, StringToVectorEmpty) {
std::vector<float> v = StringToVector<float>(" ");
EXPECT_THAT(v, ElementsAre());
EXPECT_EQ(errno, 0);
}
TEST_F(UserUtilTest, StringToVectorError) {
std::vector<float> v = StringToVector<float>("2.1 3ABCD. /123/122/113");
EXPECT_THAT(v, ElementsAre(2.1));
EXPECT_EQ(errno, EINVAL);
}
TEST_F(UserUtilTest, StringToVectorInt) {
std::vector<int> v = StringToVector<int>(" -1 3 5 6 ");
EXPECT_THAT(v, ElementsAre(-1, 3, 5, 6));
EXPECT_EQ(errno, 0);
}
TEST_F(UserUtilTest, StringToVectorString) {
auto v = StringToVector<std::string>(" abc def ");
EXPECT_THAT(v, ElementsAre("abc", "def"));
}
TEST_F(UserUtilTest, StringToVectorInvalidNumber) {
auto v = StringToVector<double>("1 0.1.2.3");
EXPECT_THAT(v, ElementsAre(1));
EXPECT_EQ(errno, EINVAL);
}
TEST_F(UserUtilTest, StringToVectorNan) {
mju_user_warning = nullptr;
auto v = StringToVector<double>("1 2 nan 3.21");
EXPECT_THAT(v[2], IsNan());
EXPECT_EQ(v[3], 3.21);
EXPECT_EQ(errno, EDOM);
}
TEST_F(UserUtilTest, StringToVectorRange) {
auto v = StringToVector<unsigned char>("-10");
EXPECT_EQ(errno, ERANGE);
}
TEST_F(UserUtilTest, VectorToString) {
std::vector<double> v = {1.2, 3.2, 5.3, 6};
EXPECT_EQ(VectorToString(v), "1.2 3.2 5.3 6");
}
TEST_F(UserUtilTest, VectorToStringEmpty) {
std::vector<double> v;
EXPECT_EQ(VectorToString(v), "");
}
} // namespace } // namespace
} // namespace mujoco } // namespace mujoco
+18 -2
View File
@@ -345,7 +345,7 @@ TEST_F(XMLReaderTest, InvalidArrayElement) {
std::array<char, 1024> error; std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size()); mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, IsNull()); ASSERT_THAT(model, IsNull());
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute 'axisangle'")); EXPECT_THAT(error.data(), HasSubstr("bad format in attribute 'axisangle'"));
EXPECT_THAT(error.data(), HasSubstr("line 5")); EXPECT_THAT(error.data(), HasSubstr("line 5"));
} }
@@ -396,10 +396,26 @@ TEST_F(XMLReaderTest, InvalidNumber) {
std::array<char, 1024> error; std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size()); mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, IsNull()); ASSERT_THAT(model, IsNull());
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute")); EXPECT_THAT(error.data(), HasSubstr("bad format in attribute 'axisangle'"));
EXPECT_THAT(error.data(), HasSubstr("line 5")); EXPECT_THAT(error.data(), HasSubstr("line 5"));
} }
TEST_F(XMLReaderTest, InvalidNumberRange) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<mesh name="mesh" file="mesh.stl" face="100000000000000000000000"/>
</asset>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, IsNull());
EXPECT_THAT(error.data(),
HasSubstr("number is too large in attribute 'face'"));
EXPECT_THAT(error.data(), HasSubstr("line 4"));
}
TEST_F(XMLReaderTest, InvalidNumberOfAttributes) { TEST_F(XMLReaderTest, InvalidNumberOfAttributes) {
static constexpr char xml[] = R"( static constexpr char xml[] = R"(
<mujoco> <mujoco>
+5 -3
View File
@@ -1238,7 +1238,7 @@ TEST_F(XMLWriterTest, SetPrecision) {
EXPECT_EQ(model->geom_size[1], model_lo->geom_size[1]); EXPECT_EQ(model->geom_size[1], model_lo->geom_size[1]);
EXPECT_NE(model->geom_size[2], model_lo->geom_size[2]); EXPECT_NE(model->geom_size[2], model_lo->geom_size[2]);
{ {
// save to XML and re-load with FullFloatPrecision // save to XML and reload with FullFloatPrecision
// expect to maintain precision // expect to maintain precision
FullFloatPrecision increase_precision; FullFloatPrecision increase_precision;
mjModel* model_hi = LoadModelFromString(SaveAndReadXml(model)); mjModel* model_hi = LoadModelFromString(SaveAndReadXml(model));
@@ -1276,8 +1276,10 @@ TEST_F(XMLWriterLocaleTest, IgnoresLocale) {
</worldbody> </worldbody>
</mujoco> </mujoco>
)"; )";
mjModel* model = LoadModelFromString(xml);
ASSERT_THAT(model, NotNull()); std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
std::string saved_xml = SaveAndReadXml(model); std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("0.1 1.23 2.345")); EXPECT_THAT(saved_xml, HasSubstr("0.1 1.23 2.345"));
mj_deleteModel(model); mj_deleteModel(model);
-57
View File
@@ -1,57 +0,0 @@
// Copyright 2024 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "src/xml/xml_util.h"
#include "test/fixture.h"
namespace mujoco {
namespace {
using XMLUtilTest = MujocoTest;
using ::testing::ElementsAre;
TEST_F(XMLUtilTest, String2VectorFloat) {
std::vector<float> v = mjXUtil::String2Vector<float>(" 1.2 3.2 5.3 6 ");
EXPECT_THAT(v, ElementsAre(1.2, 3.2, 5.3, 6));
}
TEST_F(XMLUtilTest, String2VectorEmpty) {
std::vector<float> v = mjXUtil::String2Vector<float>("");
EXPECT_THAT(v, ElementsAre());
}
TEST_F(XMLUtilTest, String2VectorError) {
std::vector<float> v = mjXUtil::String2Vector<float>("ABCD. /123/122/113");
EXPECT_THAT(v, ElementsAre());
}
TEST_F(XMLUtilTest, String2VectorInt) {
std::vector<int> v = mjXUtil::String2Vector<int>(" -1 3 5 6");
EXPECT_THAT(v, ElementsAre(-1, 3, 5, 6));
}
TEST_F(XMLUtilTest, String2VectorString) {
auto v = mjXUtil::String2Vector<std::string>(" abc def ");
EXPECT_THAT(v, ElementsAre("abc", "def"));
}
} // namespace
} // namespace mujoco