Add ReadAttrVec, ReadAttrArr, ReadAttrStr, and ReadAttrNum to XML utils.

Moving forward, the compiler objects should have encapsulated data, therefore the old ReadAttr* methods are being deprecated as they require raw pointers to read into.

No external changes other than minor improvements to ReadAttr and ReadVector.

PiperOrigin-RevId: 550880401
Change-Id: I632f40322ccd935f9af9b69e66b056c5cda6face
This commit is contained in:
Kyle Bayes
2023-07-25 07:30:19 -07:00
committed by Copybara-Service
parent 5b4b3415fa
commit 5f246ef210
3 changed files with 212 additions and 105 deletions
+160 -99
View File
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <cfloat> #include <algorithm>
#include <array>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <functional>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <optional> #include <optional>
@@ -44,7 +46,7 @@ using tinyxml2::XMLElement;
namespace mju = ::mujoco::util; namespace mju = ::mujoco::util;
template <typename T> template <typename T>
std::optional<T> ParseInfOrNan(const std::string& s) { static std::optional<T> ParseInfOrNan(const std::string& s) {
const char* str = s.c_str(); const char* str = s.c_str();
if constexpr (std::is_floating_point_v<T>) { if constexpr (std::is_floating_point_v<T>) {
T sign = 1; T sign = 1;
@@ -270,15 +272,6 @@ static void printspace(std::stringstream& str, int n, const char* space) {
} }
// max
static int _max(int a, int b) {
if (a>b) {
return a;
} else {
return b;
}
}
// print schema as text // print schema as text
void mjXSchema::Print(std::stringstream& str, int level) { void mjXSchema::Print(std::stringstream& str, int level) {
@@ -294,11 +287,11 @@ void mjXSchema::Print(std::stringstream& str, int level) {
} }
// attributes // attributes
int cnt = _max(baselen, 30); int cnt = std::max(baselen, 30);
for (int i=0; i<(int)attr.size(); i++) { for (int i=0; i<(int)attr.size(); i++) {
if (cnt>60) { if (cnt>60) {
str << "\n"; str << "\n";
printspace(str, (cnt = _max(30, baselen)), " "); printspace(str, (cnt = std::max(30, baselen)), " ");
} }
@@ -508,6 +501,63 @@ XMLElement* mjXSchema::Check(XMLElement* elem, int level) {
//---------------------------------- class mjXUtil implementation ---------------------------------- //---------------------------------- class mjXUtil implementation ----------------------------------
// helper function to read multiple numerical values from an attribute
// return false if the entire attribute wasn't read (max was reached)
// throw error if syntax error while trying to read numerical data
template<typename T>
bool mjXUtil::ReadAttrValues(XMLElement* elem, const char* attr,
std::function<void (int, T)> push, int max) {
const char* pstr = elem->Attribute(attr);
T item;
if (pstr == nullptr) {
return true;
}
// get input stream
std::string str = std::string(pstr);
std::istringstream strm(str);
std::string token;
// read numbers
for (int i = 0; (max < 0 || i < max) && !strm.eof(); ++i) {
strm >> token;
std::istringstream token_strm(token);
token_strm >> item;
if (token_strm.fail() || !token_strm.eof()) {
// C++ standard libraries do not always parse inf and nan as valid floating point values.
std::optional<T> maybe_result = ParseInfOrNan<T>(token);
if (maybe_result.has_value()) {
item = maybe_result.value();
} else {
throw mjXError(elem, "problem reading attribute '%s'", attr);
}
}
push(i, item);
if constexpr (std::is_floating_point_v<T>) {
if (std::isnan(item)) {
mju_warning("XML contains a 'NaN'. Please check it carefully.");
}
}
// clear any trailing whitespace
strm >> std::ws;
}
return strm.eof();
}
template bool mjXUtil::ReadAttrValues(XMLElement* elem, const char* attr,
std::function<void (int, double)> push, int max);
template bool mjXUtil::ReadAttrValues(XMLElement* elem, const char* attr,
std::function<void (int, float)> push, int max);
template bool mjXUtil::ReadAttrValues(XMLElement* elem, const char* attr,
std::function<void (int, int)> push, int max);
template bool mjXUtil::ReadAttrValues(XMLElement* elem, const char* attr,
std::function<void (int, mjtByte)> push, int max);
// compare two vectors // compare two vectors
template<typename T> template<typename T>
bool mjXUtil::SameVector(const T* vec1, const T* vec2, int n) { bool mjXUtil::SameVector(const T* vec1, const T* vec2, int n) {
@@ -557,6 +607,76 @@ string mjXUtil::FindValue(const mjMap* map, int mapsz, int value) {
// if attribute is present, return vector of numerical data
template<typename T>
std::optional<std::vector<T>> mjXUtil::ReadAttrVec(XMLElement* elem, const char* attr,
bool required) {
std::vector<T> vec;
ReadAttrValues<T>(elem, attr, [&](int i, T num) { vec.push_back(num); });
if (!vec.size()) {
if (required) {
throw mjXError(elem, "required attribute missing: '%s'", attr);
} else {
return std::nullopt;
}
}
return vec;
}
template std::optional<std::vector<double>>
mjXUtil::ReadAttrVec(XMLElement* elem, const char* attr, bool required);
template std::optional<std::vector<float>>
mjXUtil::ReadAttrVec(XMLElement* elem, const char* attr, bool required);
template std::optional<std::vector<int>>
mjXUtil::ReadAttrVec(XMLElement* elem, const char* attr, bool required);
template std::optional<std::vector<mjtByte>>
mjXUtil::ReadAttrVec(XMLElement* elem, const char* attr, bool required);
// if attribute is present, return attribute as a string
std::optional<std::string> mjXUtil::ReadAttrStr(XMLElement* elem, const char* attr,
bool required) {
const char* pstr = elem->Attribute(attr);
// check if attribute exists
if (pstr == nullptr) {
if (required) {
throw mjXError(elem, "required attribute missing: '%s'", attr);
} else {
return std::nullopt;
}
}
return std::string(pstr);
}
// if attribute is present, return numerical value of attribute
template<typename T>
std::optional<T> mjXUtil::ReadAttrNum(XMLElement* elem, const char* attr,
bool required) {
auto maybe_arr = ReadAttrArr<T, 1>(elem, attr, required);
if (!maybe_arr.has_value()) {
return std::nullopt;
}
return maybe_arr.value()[0];
}
template std::optional<double>
mjXUtil::ReadAttrNum(XMLElement* elem, const char* attr, bool required);
template std::optional<float>
mjXUtil::ReadAttrNum(XMLElement* elem, const char* attr, bool required);
template std::optional<int>
mjXUtil::ReadAttrNum(XMLElement* elem, const char* attr, bool required);
template std::optional<mjtByte>
mjXUtil::ReadAttrNum(XMLElement* elem, const char* attr, bool required);
// read attribute "attr" of element "elem" // read attribute "attr" of element "elem"
// "len" is the number of floats or doubles to be read // "len" is the number of floats or doubles to be read
// the content is returned in "text", the numeric data in "data" // the content is returned in "text", the numeric data in "data"
@@ -564,59 +684,23 @@ string mjXUtil::FindValue(const mjMap* map, int mapsz, int value) {
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, string& text, bool required, bool exact) {
const char* pstr = elem->Attribute(attr); auto maybe_vec = ReadAttrVec<T>(elem, attr, required);
if (!maybe_vec.has_value()) {
// check if attribute exists return 0;
if (!pstr) {
if (required) {
throw mjXError(elem, "required attribute missing: '%s'", attr);
} else {
return 0;
}
} }
// convert to string
text = string(pstr);
// get input stream
istringstream strm(text);
std::string token;
// read numbers
int i = 0;
while (!strm.eof() && i < len) {
strm >> token;
istringstream token_strm(token);
token_strm >> data[i];
if (token_strm.fail() || !token_strm.eof()) {
// C++ standard libraries do not always parse inf and nan as valid floating point values.
std::optional<T> maybe_result = ParseInfOrNan<T>(token);
if (maybe_result.has_value()) {
data[i] = *maybe_result;
} else {
throw mjXError(elem, "problem reading attribute '%s'", attr);
}
}
if constexpr (std::is_floating_point_v<T>) {
if (std::isnan(data[i])) {
mju_warning("XML contains a 'NaN'. Please check it carefully.");
}
}
++i;
}
strm >> std::ws;
// check if there is not enough data // check if there is not enough data
if (exact && i < len) { if (exact && maybe_vec->size() < len) {
throw mjXError(elem, "attribute '%s' does not have enough data", attr); throw mjXError(elem, "attribute '%s' does not have enough data", attr);
} }
// check if there is too much data // check if there is too much data
if (!strm.eof()) { if (maybe_vec->size() > len) {
throw mjXError(elem, "attribute '%s' has too much data", attr); throw mjXError(elem, "attribute '%s' has too much data", attr);
} }
return i; std::copy(maybe_vec->begin(), maybe_vec->end(), data);
return maybe_vec->size();
} }
template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len, template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
@@ -636,59 +720,36 @@ template int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len
// 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) { vector<double>& vec, string& text, bool required) {
double buffer[1000]; auto maybe_vec = ReadAttrVec<double>(elem, attr, required);
int n = ReadAttr(elem, attr, 1000, buffer, text, required, false); if (!maybe_vec.has_value()) {
if (n>0) { return 0;
vec.resize(n);
memcpy(vec.data(), buffer, n*sizeof(double));
} }
return n;
vec = std::move(maybe_vec.value());
return vec.size();
} }
// read text field // read text field
bool mjXUtil::ReadAttrTxt(XMLElement* elem, const char* attr, string& text, bool required) { bool mjXUtil::ReadAttrTxt(tinyxml2::XMLElement* elem, const char* attr, string& text, bool required) {
const char* pstr = elem->Attribute(attr); auto maybe_str = ReadAttrStr(elem, attr, required);
if (!maybe_str.has_value()) {
// check if attribute exists return false;
if (!pstr) {
if (required) {
throw mjXError(elem, "required attribute missing: '%s'", attr);
} else {
return false;
}
} }
// read text text = maybe_str.value();
text = string(pstr);
return true; return true;
} }
// read single int // read single int
bool mjXUtil::ReadAttrInt(XMLElement* elem, const char* attr, int* data, bool required) { bool mjXUtil::ReadAttrInt(XMLElement* elem, const char* attr, int* data, bool required) {
const char* pstr = elem->Attribute(attr); auto maybe_int = ReadAttrNum<int>(elem, attr, required);
if (!maybe_int.has_value()) {
// check if attribute exists return false;
if (!pstr) {
if (required) {
throw mjXError(elem, "required attribute missing: '%s'", attr);
} else {
return false;
}
} }
// convert to int, check *data = maybe_int.value();
int buffer[2] = {0, 0};
if (sscanf(pstr, "%d", buffer) != 1) {
throw mjXError(elem, "single int expected in attribute %s", attr);
}
// copy data
*data = buffer[0];
return true; return true;
} }
@@ -796,15 +857,15 @@ XMLElement* mjXUtil::FindSubElem(XMLElement* elem, string name, bool required) {
bool mjXUtil::MapValue(XMLElement* elem, const char* attr, int* data, bool mjXUtil::MapValue(XMLElement* elem, const char* attr, int* data,
const mjMap* map, int mapSz, bool required) { const mjMap* map, int mapSz, bool required) {
// get attribute text // get attribute text
string text; auto maybe_text = ReadAttrStr(elem, attr, required);
if (!ReadAttrTxt(elem, attr, text, required)) { if (!maybe_text.has_value()) {
return false; return false;
} }
// find keyword in map // find keyword in map
int value = FindKey(map, mapSz, text); int value = FindKey(map, mapSz, maybe_text.value());
if (value<0) { if (value < 0) {
throw mjXError(elem, "invalid keyword: '%s'", text.c_str()); throw mjXError(elem, "invalid keyword: '%s'", maybe_text->c_str());
} }
// copy // copy
+51 -5
View File
@@ -16,7 +16,9 @@
#ifndef MUJOCO_SRC_XML_XML_UTIL_H_ #ifndef MUJOCO_SRC_XML_XML_UTIL_H_
#define MUJOCO_SRC_XML_XML_UTIL_H_ #define MUJOCO_SRC_XML_XML_UTIL_H_
// stl #include <array>
#include <functional>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <sstream> #include <sstream>
@@ -98,21 +100,60 @@ class mjXUtil {
// find value in map, return key ("": not found) // find value in map, return key ("": not found)
static std::string FindValue(const mjMap* map, int mapsz, int value); static std::string FindValue(const mjMap* map, int mapsz, int value);
// read any type from attribute, return number read // 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,
bool required = false);
// if attribute is present, return attribute as a string
static std::optional<std::string> ReadAttrStr(tinyxml2::XMLElement* elem, const char* attr,
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,
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,
bool required = false) {
std::array<T, N> arr;
int n = 0;
if (!ReadAttrValues<T>(elem, attr, [&](int i, T num) { arr[i] = num; n++; }, N)) {
throw mjXError(elem, "attribute '%s' has too much data", attr);
}
if (!n) {
if (required) {
throw mjXError(elem, "required attribute missing: '%s'", attr);
} else {
return std::nullopt;
}
}
if (n < N) {
throw mjXError(elem, "attribute '%s' does not have enough data", attr);
}
return arr;
}
// deprecated: use ReadAttrVec or ReadAttrArr
template<typename T> template<typename T>
static int ReadAttr(tinyxml2::XMLElement* elem, const char* attr, const int len, static int ReadAttr(tinyxml2::XMLElement* elem, const char* attr, const int len,
T* data, std::string& text, T* data, std::string& text,
bool required = false, bool exact = true); bool required = false, bool exact = true);
// read DOUBLE array into C++ vector, return number read // deprecated: use ReadAttrVec
static int ReadVector(tinyxml2::XMLElement* elem, const char* attr, static int ReadVector(tinyxml2::XMLElement* elem, const char* attr,
std::vector<double>& vec, std::string& text, bool required = false); std::vector<double>& vec, std::string& text, bool required = false);
// read text attribute // deprecated: use ReadAttrStr
static bool ReadAttrTxt(tinyxml2::XMLElement* elem, const char* attr, std::string& text, static bool ReadAttrTxt(tinyxml2::XMLElement* elem, const char* attr, std::string& text,
bool required = false); bool required = false);
// read int attribute // deprecated: use ReadAttrNum
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);
@@ -155,6 +196,11 @@ class mjXUtil {
// write attribute- keyword // write attribute- keyword
static void WriteAttrKey(tinyxml2::XMLElement* elem, std::string name, static void WriteAttrKey(tinyxml2::XMLElement* elem, std::string name,
const mjMap* map, int mapsz, int data, int def = -12345); const mjMap* map, int mapsz, int data, int def = -12345);
private:
template<typename T>
static bool ReadAttrValues(tinyxml2::XMLElement* elem, const char* attr,
std::function<void (int, T)> push, int max = -1);
}; };
#endif // MUJOCO_SRC_XML_XML_UTIL_H_ #endif // MUJOCO_SRC_XML_XML_UTIL_H_
+1 -1
View File
@@ -317,7 +317,7 @@ TEST_F(XMLReaderTest, InvalidArrayLength) {
<mujoco> <mujoco>
<worldbody> <worldbody>
<body> <body>
<geom size="1" axisangle="1 0 0 0 asd"/> <geom size="1" axisangle="1 0 0 0 1"/>
</body> </body>
</worldbody> </worldbody>
</mujoco> </mujoco>