790f8fac30
The hand-written MJCF[] table in xml_native_reader.cc is replaced by
mjcf_table.inc, emitted from mjcf.schema by generate_mjcf_table.py and
checked for freshness by doc_test. nMJCF is now self-sizing. The two
tables are identical as trees of (tag, cardinality, attribute-set);
within-row attribute order changes where the schema factors shared
groups and projects default-context rows, and top-level rows follow
the schema's dependency order -- neither affects validation, which is
set-based, nor XMLschema.rst, whose generator orders sections itself
(regenerated here, reading the .inc instead of the reader source).
The schema's constraint declarations become enforcement: the emitter
writes a companion MJCF_constraints[] array (row-indexed into MJCF[]),
and mjXSchema::Check evaluates each element's constraints after its
attribute check, with uniform messages derived from the declaration:
"at most one of 'fovy', 'sensorsize' can be specified", "attributes
'reftype', 'refname' must be specified together", and so on.
Multi-attribute bundles render as ('site1', 'site2').
Fifteen hand-written co-occurrence checks across fourteen elements are
deleted -- connect/weld semantics mixing and completeness, the actuator
transmission mutex, camera fovy/sensorsize, light directional/type,
inertial fullinertia-versus-orientation, rangefinder and the distance
family, contact's matching criteria, user-sensor pairing, the frame
family's reftype/refname, size memory exclusivities, mesh builtin
exclusions, and attach body/frame (newly declared). Tests assert the
uniform messages.
Two findings along the way: sensorsize-requires-resolution is a
value-level compiler rule (positive resolution), not a presence rule --
a presence constraint would be wrong and is not declared; and Size()'s
nstack/njmax range checks tested the spec value before assignment, so
they never validated the parsed value -- now they do.
Verified by compiling all 81 models in the model/ corpus.
PiperOrigin-RevId: 958064622
Change-Id: I802cf5c0aee08a62926e36a281320ff9e34c0668
247 lines
9.1 KiB
C++
247 lines
9.1 KiB
C++
// Copyright 2021 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.
|
|
|
|
|
|
#ifndef MUJOCO_SRC_XML_XML_UTIL_H_
|
|
#define MUJOCO_SRC_XML_XML_UTIL_H_
|
|
|
|
#include <array>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <sstream>
|
|
|
|
#include <mujoco/mujoco.h>
|
|
#include "user/user_util.h"
|
|
#include "tinyxml2.h"
|
|
|
|
// error string copy
|
|
void mjCopyError(char* dst, const char* src, int maxlen);
|
|
|
|
using tinyxml2::XMLElement;
|
|
|
|
XMLElement* FirstChildElement(XMLElement* e, const char* name = nullptr);
|
|
XMLElement* NextSiblingElement(XMLElement* e, const char* name = nullptr);
|
|
|
|
// XML Error info
|
|
class [[nodiscard]] mjXError {
|
|
public:
|
|
mjXError(const tinyxml2::XMLElement* elem = 0,
|
|
const char* msg = 0,
|
|
const char* str = 0,
|
|
int pos = 0);
|
|
~mjXError() = default;
|
|
|
|
char message[1000]; // error message
|
|
};
|
|
|
|
|
|
// presence constraint over an element's attributes, generated from the
|
|
// schema into the grammar table's companion array. spec holds attribute
|
|
// bundles, space-joined within a bundle and '|'-separated between them;
|
|
// kind: e=exclusive (at most one bundle present), t=together (all or
|
|
// none), r=requires (first needs second), o=oneof (at least one bundle
|
|
// complete).
|
|
struct mjXConstraintDef {
|
|
int row; // index of the element's row in the grammar table
|
|
char kind;
|
|
const char* spec;
|
|
};
|
|
|
|
|
|
// Custom XML file validation
|
|
class mjXSchema {
|
|
public:
|
|
mjXSchema(std::vector<const char*> schema[], unsigned nrow,
|
|
const mjXConstraintDef* constraints = nullptr,
|
|
int nconstraint = 0, int first_row = 0);
|
|
|
|
std::string GetError(); // return error
|
|
void Print(std::stringstream& str, int level) const; // print schema
|
|
void PrintHTML(std::stringstream& str, int level, bool pad) const;
|
|
|
|
bool NameMatch(tinyxml2::XMLElement* elem, int level); // does name match
|
|
tinyxml2::XMLElement* Check(tinyxml2::XMLElement* elem, int level); // validator
|
|
|
|
private:
|
|
std::string name_; // element name
|
|
char type_; // element type: '?', '!', '*', 'R'
|
|
std::set<std::string> attr_; // allowed attributes
|
|
std::vector<mjXSchema> subschema_; // allowed child elements
|
|
|
|
std::vector<const mjXConstraintDef*> constraints_; // constraints here
|
|
tinyxml2::XMLElement* CheckConstraints(tinyxml2::XMLElement* elem);
|
|
|
|
int refcnt_ = 0; // refcount used for validation
|
|
std::string error; // error from constructor or Check
|
|
};
|
|
|
|
|
|
// key(string) : value(int) map
|
|
struct _mjMap {
|
|
const char* key;
|
|
int value;
|
|
};
|
|
typedef struct _mjMap mjMap;
|
|
|
|
|
|
// XML read and write utility functions
|
|
class mjXUtil {
|
|
public:
|
|
mjXUtil() = default;
|
|
virtual ~mjXUtil() = default;
|
|
|
|
// compare two vectors
|
|
template<typename T>
|
|
static bool SameVector(const T* vec1, const T* vec2, int n);
|
|
|
|
// find key in map, return value (-1: not found)
|
|
static int FindKey(const mjMap* map, int mapsz, std::string key);
|
|
|
|
// find value in map, return key ("": not found)
|
|
static std::string FindValue(const mjMap* map, int mapsz, int value);
|
|
|
|
// 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 attribute as a filename
|
|
static std::optional<mujoco::user::FilePath>
|
|
ReadAttrFile(tinyxml2::XMLElement* elem, const char* attr,
|
|
const mjVFS* vfs,
|
|
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,
|
|
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>
|
|
static int ReadAttr(tinyxml2::XMLElement* elem, const char* attr, int len,
|
|
T* data, std::string& text,
|
|
bool required = false, bool exact = true);
|
|
|
|
static int ReadQuat(tinyxml2::XMLElement* elem, const char* attr, double* data,
|
|
std::string& text, bool required = false);
|
|
|
|
// deprecated: use ReadAttrVec
|
|
static int ReadVector(tinyxml2::XMLElement* elem, const char* attr,
|
|
std::vector<double>& vec, std::string& text, bool required = false);
|
|
|
|
// deprecated: use ReadAttrStr
|
|
static bool ReadAttrTxt(tinyxml2::XMLElement* elem, const char* attr, std::string& text,
|
|
bool required = false);
|
|
|
|
// deprecated: use ReadAttrNum
|
|
static bool ReadAttrInt(tinyxml2::XMLElement* elem, const char* attr, int* data,
|
|
bool required = false);
|
|
|
|
// write vector<float> to string
|
|
static void Vector2String(std::string& txt, const std::vector<float>& vec, int ncol = 0);
|
|
|
|
|
|
// find subelement with given name, make sure it is unique
|
|
static tinyxml2::XMLElement* FindSubElem(tinyxml2::XMLElement* elem, std::string name,
|
|
bool required = false);
|
|
|
|
// find attribute, translate key, return int value
|
|
static bool MapValue(tinyxml2::XMLElement* elem, const char* attr, int* data,
|
|
const mjMap* map, int mapSz, bool required = false);
|
|
|
|
template <typename T>
|
|
static bool MapValue(tinyxml2::XMLElement* elem, const char* attr, T* data,
|
|
const mjMap* map, int mapSz, bool required = false) {
|
|
int value;
|
|
if (MapValue(elem, attr, &value, map, mapSz, required)) {
|
|
*data = static_cast<T>(value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// find attribute, translate unique space-separated keys to data, return number of keys found
|
|
static int MapValues(tinyxml2::XMLElement* elem, const char* attr, int* data,
|
|
const mjMap* map, int mapSz, bool required = false);
|
|
|
|
// write attribute- any type
|
|
template<typename T>
|
|
static void WriteAttr(tinyxml2::XMLElement* elem, std::string name, int n, const T* data,
|
|
const T* def = 0, bool trim = false);
|
|
|
|
// write vector<double> attribute, with and without default
|
|
static void WriteVector(tinyxml2::XMLElement* elem, std::string name,
|
|
const std::vector<double>& vec);
|
|
static void WriteVector(tinyxml2::XMLElement* elem, std::string name,
|
|
const std::vector<double>& vec, const std::vector<double>& def);
|
|
|
|
// write attribute- string
|
|
static void WriteAttrTxt(tinyxml2::XMLElement* elem, std::string name, std::string value);
|
|
|
|
// write attribute- single int
|
|
static void WriteAttrInt(tinyxml2::XMLElement* elem, std::string name, int data, int def = -12345);
|
|
|
|
// write attribute- keyword
|
|
static void WriteAttrKey(tinyxml2::XMLElement* elem, std::string name,
|
|
const mjMap* map, int mapsz, int data, int def = -12345);
|
|
|
|
// write attribute- space-separated keywords
|
|
static void WriteAttrKeys(XMLElement* elem, std::string name, const mjMap* map,
|
|
int mapsz, int* data, int ndata, 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_
|