Files
Mujoco_WASM/src/xml/xml_api.cc
T
Yuval Tassa 6f8bb5ef55 Refactor compiler warning handling.
Compiler warnings are now accumulated in a vector of strings within the mjSpec object. New API functions `mjs_numWarnings` and `mjs_getWarning` are added to access these warnings. The compiler's log handler now chains warnings to the global log handler, ensuring they are still displayed immediately. Call sites in `mj_loadXML`, `mj_compile`, and the Python and WASM bindings have been updated to use the new warning API.

PiperOrigin-RevId: 933361650
Change-Id: I47cab98a460c57b0898c0a1a43fce2a5b9648eb1
2026-06-16 16:28:33 -07:00

215 lines
5.7 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.
#include "xml/xml_api.h"
#include <array>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <functional>
#include <memory>
#include <sstream>
#include <string>
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h>
#include "engine/engine_io.h"
#include "user/user_resource.h"
#include "xml/xml.h"
#include "xml/xml_global.h"
#include "xml/xml_native_reader.h"
#include "xml/xml_util.h"
//---------------------------------- Functions -----------------------------------------------------
// parse XML file in MJCF or URDF format, compile it, return low-level model
// if vfs is not NULL, look up files in vfs before reading from disk
// error can be NULL; otherwise assumed to have size error_sz
mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
char* error, int error_sz) {
// parse new model
std::unique_ptr<mjSpec, std::function<void(mjSpec*)> > spec(
ParseXML(filename, vfs, error, error_sz),
[](mjSpec* s) {
mj_deleteSpec(s);
});
if (!spec) {
return nullptr;
}
// compile new model
mjModel* m = mj_compile(spec.get(), vfs);
if (!m) {
mjCopyError(error, mjs_getError(spec.get()), error_sz);
return nullptr;
}
// handle compile warning
int num_warnings = mjs_numWarnings(spec.get());
if (num_warnings > 0) {
std::string all_warnings;
for (int i = 0; i < num_warnings; ++i) {
if (!all_warnings.empty()) {
all_warnings += '\n';
}
all_warnings += mjs_getWarning(spec.get(), i);
}
mjCopyError(error, all_warnings.c_str(), error_sz);
} else if (error) {
error[0] = '\0';
}
// clear old and assign new
SetGlobalXmlSpec(spec.release());
return m;
}
// update XML data structures with info from low-level model, save as MJCF
// returns 1 if successful, 0 otherwise
// error can be NULL; otherwise assumed to have size error_sz
int mj_saveLastXML(const char* filename, const mjModel* m, char* error, int error_sz) {
FILE *fp = stdout;
if (filename != nullptr && filename[0] != '\0') {
fp = fopen(filename, "w");
if (!fp) {
mjCopyError(error, "File not found", error_sz);
return 0;
}
}
const std::string result = GetGlobalXmlSpec(m, error, error_sz);
if (!result.empty()) {
fprintf(fp, "%s", result.c_str());
}
if (fp != stdout) {
fclose(fp);
}
return !result.empty();
}
// free last XML
void mj_freeLastXML(void) {
SetGlobalXmlSpec();
}
// print internal XML schema as plain text or HTML, with style-padding or &nbsp;
int mj_printSchema(const char* filename, char* buffer, int buffer_sz, int flg_html, int flg_pad) {
// print to stringstream
mjXReader reader;
std::stringstream str;
reader.PrintSchema(str, flg_html != 0, flg_pad != 0);
// filename given: write to file
if (filename) {
std::ofstream file;
file.open(filename);
file << str.str();
file.close();
}
// buffer given: write to buffer
if (buffer && buffer_sz) {
strncpy(buffer, str.str().c_str(), buffer_sz);
buffer[buffer_sz-1] = 0;
}
// return string length
return str.str().size();
}
// load model from binary MJB resource
mjModel* mj_loadModel(const char* filename, const mjVFS* vfs) {
std::array<char, 1024> error;
mjResource* resource = mju_openResource("", filename, vfs,
error.data(), error.size());
if (resource == nullptr) {
mju_warning("%s", error.data());
return nullptr;
}
const void* buffer = NULL;
int buffer_sz = mju_readResource(resource, &buffer);
if (buffer_sz < 1) {
mju_closeResource(resource);
return nullptr;
}
mjModel* m = mj_loadModelBuffer(buffer, buffer_sz);
mju_closeResource(resource);
return m;
}
// parse spec from file
mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int error_sz) {
return ParseXML(filename, vfs, error, error_sz);
}
// parse spec from string
mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz) {
return ParseSpecFromString(xml, vfs, error, error_sz);
}
// save spec to XML file, return 0 on success, -1 otherwise
int mj_saveXML(const mjSpec* s, const char* filename, char* error, int error_sz) {
// cast to mjSpec since WriteXML can in principle perform mj_copyBack (not here)
std::string result = WriteXML(NULL, (mjSpec*)s, error, error_sz);
if (result.empty()) {
return -1;
}
std::ofstream file;
file.open(filename);
file << result;
file.close();
return 0;
}
// save spec to XML string, return 0 on success, -1 on failure
// if length of the output buffer is too small, returns the required size
int mj_saveXMLString(const mjSpec* s, char* xml, int xml_sz, char* error, int error_sz) {
std::string result = WriteXML(NULL, (mjSpec*)s, error, error_sz);
if (result.empty()) {
return -1;
} else if (result.size() >= xml_sz) {
std::string error_msg = "Output string too short, should be at least " +
std::to_string(result.size()+1);
mjCopyError(error, error_msg.c_str(), error_sz);
return result.size();
}
result.copy(xml, xml_sz);
xml[result.size()] = 0;
return 0;
}