diff --git a/sdf/1.12/CMakeLists.txt b/sdf/1.12/CMakeLists.txt
index 9d02c42bf..1f1ceb3dc 100644
--- a/sdf/1.12/CMakeLists.txt
+++ b/sdf/1.12/CMakeLists.txt
@@ -94,6 +94,11 @@ add_custom_command(
list(APPEND SDF_SCHEMA "${CMAKE_CURRENT_BINARY_DIR}/types.xsd")
+configure_file(
+ ${CMAKE_SOURCE_DIR}/sdf/catalog.xml.in
+ ${CMAKE_CURRENT_BINARY_DIR}/catalog.xml
+ @ONLY)
+
add_custom_target(schema1_12 ALL DEPENDS ${SDF_SCHEMA})
set_source_files_properties(${SDF_SCHEMA} PROPERTIES GENERATED TRUE)
diff --git a/sdf/catalog.xml.in b/sdf/catalog.xml.in
new file mode 100644
index 000000000..e099fb3b7
--- /dev/null
+++ b/sdf/catalog.xml.in
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt
index afaa83ad2..42c236d4c 100644
--- a/test/integration/CMakeLists.txt
+++ b/test/integration/CMakeLists.txt
@@ -71,31 +71,7 @@ endif()
find_program(XMLLINT_EXE xmllint)
if (EXISTS ${XMLLINT_EXE})
- # get xmllint version
- execute_process(
- COMMAND ${XMLLINT_EXE} --version
- ERROR_VARIABLE XMLLINT_VERSION_OUTPUT
- )
- # typical output: "/usr/bin/xmllint: using libxml version 20913\ncompiled with: ..."
- # version is formatted as a single integer, with 20913 representing 2.9.13
- string(REGEX MATCH "using libxml version [0-9]+" XMLLINT_VERSION_STRING "${XMLLINT_VERSION_OUTPUT}")
- string(REPLACE "using libxml version " "" XMLLINT_VERSION "${XMLLINT_VERSION_STRING}")
- if("${XMLLINT_VERSION}" STREQUAL "")
- message(WARNING "Unable to identify xmllint version. schema_test won't be run")
- elseif("${XMLLINT_VERSION}" LESS 10000)
- message(WARNING "Old version of xmllint (${XMLLINT_VERSION_STRING}) detected. schema_test won't be run")
- elseif("${XMLLINT_VERSION}" LESS 21500)
- # schema test is broken with very new versions of xmllint
- # https://github.com/gazebosim/sdformat/issues/1656
- # enable schema test if xmllint is older than 2.15.0
- set (tests ${tests} schema_test.cc)
- else()
- # TODO: convert to WARNING when https://github.com/gazebosim/sdformat/issues/1656
- # is resolved on supported platforms. It currently fails on Ubuntu 26.04
- message(STATUS "WARNING: xmllint version (${XMLLINT_VERSION}) is too new; "
- "schema_test won't be run. "
- "See https://github.com/gazebosim/sdformat/issues/1656")
- endif()
+ set (tests ${tests} schema_test.cc)
else()
message(WARNING "xmllint not found. schema_test won't be run")
endif()
@@ -111,6 +87,7 @@ if (TARGET ${TEST_TYPE}_schema_test)
target_compile_definitions(${TEST_TYPE}_schema_test
PRIVATE
-DSDF_ROOT_SCHEMA="${PROJECT_BINARY_DIR}/sdf/${SDF_PROTOCOL_VERSION}/root.xsd"
+ -DSDF_XML_CATALOG="${PROJECT_BINARY_DIR}/sdf/${SDF_PROTOCOL_VERSION}/catalog.xml"
)
if (EXISTS ${XMLLINT_EXE})
diff --git a/test/integration/schema_test.cc b/test/integration/schema_test.cc
index 7a8392d53..389d12b13 100644
--- a/test/integration/schema_test.cc
+++ b/test/integration/schema_test.cc
@@ -21,6 +21,8 @@
#include
+#include
+
#include "sdf/parser.hh"
#include "test_config.hh"
@@ -31,8 +33,10 @@ class SDFSchemaGenerator : public testing::Test
public:
void runXMLlint(const std::string & model)
{
- const auto sdfRootSchema = sdf::filesystem::append(SDF_ROOT_SCHEMA);
- std::string xmllintCmd = "xmllint --noout --schema " +
+ const std::string sdfCatalog = SDF_XML_CATALOG;
+ gz::utils::setenv("XML_CATALOG_FILES", sdfCatalog.c_str());
+ const std::string sdfRootSchema = SDF_ROOT_SCHEMA;
+ std::string xmllintCmd = "xmllint --catalogs --noout --schema " +
sdfRootSchema + " " + model;
std::cout << "CMD[" << xmllintCmd << "]\n";
if (system(xmllintCmd.c_str()) != 0)
diff --git a/tools/xmlschema.py b/tools/xmlschema.py
index b3518f9ec..c638854a1 100755
--- a/tools/xmlschema.py
+++ b/tools/xmlschema.py
@@ -128,7 +128,15 @@ def print_include_ref(element: ElementTree.Element, sdf_root_dir: str) -> List[s
include_tree = ElementTree.parse(sdf_path)
root = include_tree.getroot()
include_element_name = root.attrib["name"]
- lines.append(f"")
+
+ elem_reqd = get_attribute(element, "required")
+ if elem_reqd:
+ min_occurs, max_occurs = SDF_REQUIRED_TO_MIN_MAX_OCCURS[elem_reqd]
+ lines.append(f"")
+ lines.append(f" ")
+ lines.append("")
+ else:
+ lines.append(f"")
return lines
@@ -147,7 +155,7 @@ def print_plugin_element(element: ElementTree.Element) -> List[str]:
return lines
-def print_element(element: ElementTree.Element) -> List[str]:
+def print_element(element: ElementTree.Element, sdf_root_dir: str) -> List[str]:
"""
Print a child element of the sdf definition
"""
@@ -160,30 +168,58 @@ def print_element(element: ElementTree.Element) -> List[str]:
if elem_type and is_std_type(elem_type):
elem_type = xsd_type_string(elem_type)
+ elements = element.findall("element")
+ attributes = element.findall("attribute")
+ includes = element.findall("include")
+
if not elem_reqd:
raise RuntimeError("Cannot process element missing 'required' attribute")
min_occurs, max_occurs = SDF_REQUIRED_TO_MIN_MAX_OCCURS[elem_reqd]
lines.append(f"")
- if elem_type is None:
+ complex_type = len(elements) > 0 or len(attributes) > 0 or len(includes) > 0
+
+ if not complex_type and elem_type:
+ lines.append(f"")
+ else:
lines.append(f"")
- lines.extend(indent_lines(print_documentation(element), 2))
+
+ lines.extend(indent_lines(print_documentation(element), 2))
+
+ if complex_type:
lines.append(" ")
- lines.append(" ")
- for child_element in element.findall("element"):
- lines.extend(indent_lines(print_element(child_element), 6))
+ if elem_type:
+ lines.append(" ")
+ lines.append(f" ")
+ for attribute in attributes:
+ lines.extend(indent_lines(print_attribute(attribute), 8))
+ lines.append(" ")
+ lines.append(" ")
+ else:
+ if len(elements) or len(includes):
+ lines.append(" ")
+
+ for child_element in elements:
+ if "copy_data" in child_element.attrib:
+ element_lines = print_plugin_element(child_element)
+ lines.extend(indent_lines(element_lines, 4))
+ else:
+ element_lines = print_element(child_element, sdf_root_dir)
+ lines.extend(indent_lines(element_lines, 6))
+
+ for include_element in includes:
+ element_lines = print_include_ref(include_element, sdf_root_dir)
+ lines.extend(indent_lines(element_lines, 6))
- lines.append(" ")
+ if len(elements) or len(includes):
+ lines.append(" ")
- for attribute in element.findall("attribute"):
- lines.extend(indent_lines(print_attribute(attribute), 4))
+ for attribute in attributes:
+ lines.extend(indent_lines(print_attribute(attribute), 4))
lines.append(" ")
- else:
- lines.append(f"")
- lines.extend(indent_lines(print_documentation(element), 2))
lines.append("")
lines.append("")
@@ -231,6 +267,9 @@ def print_xsd(element: ElementTree.Element, sdf_root_dir: str) -> List[str]:
elem_name = get_attribute(element, "name")
elem_type = get_attribute(element, "type")
+ if elem_type and is_std_type(elem_type):
+ elem_type = xsd_type_string(elem_type)
+
elements = element.findall("element")
attributes = element.findall("attribute")
includes = element.findall("include")
@@ -240,44 +279,57 @@ def print_xsd(element: ElementTree.Element, sdf_root_dir: str) -> List[str]:
""
)
- # Reference any includes in the SDF file
- for include in includes:
- lines.extend(print_include(include))
+ # Reference all includes in the SDF file (including nested ones)
+ all_includes = element.findall(".//include")
+ included_files = set()
+ for include in all_includes:
+ filename = get_attribute(include, "filename")
+ if filename and filename not in included_files:
+ included_files.add(filename)
+ lines.extend(print_include(include))
- if len(elements) or len(attributes) or len(includes):
+ complex_type = len(elements) > 0 or len(attributes) > 0 or len(includes) > 0
+
+ if not complex_type and elem_type:
+ lines.append(f"")
+ else:
lines.append(f"")
- lines.append(" ")
- if elem_name != "plugin" and (len(elements) or len(includes)):
- lines.append(" ")
+ if complex_type:
+ lines.append(" ")
- for child_element in elements:
- if "copy_data" in child_element.attrib:
- element_lines = print_plugin_element(child_element)
- lines.extend(indent_lines(element_lines, 4))
- else:
- element_lines = print_element(child_element)
+ if elem_type:
+ lines.append(" ")
+ lines.append(f" ")
+ for attribute in attributes:
+ lines.extend(indent_lines(print_attribute(attribute), 8))
+ lines.append(" ")
+ lines.append(" ")
+ else:
+ if elem_name != "plugin" and (len(elements) or len(includes)):
+ lines.append(" ")
+
+ for child_element in elements:
+ if "copy_data" in child_element.attrib:
+ element_lines = print_plugin_element(child_element)
+ lines.extend(indent_lines(element_lines, 4))
+ else:
+ element_lines = print_element(child_element, sdf_root_dir)
+ lines.extend(indent_lines(element_lines, 6))
+
+ for include_element in includes:
+ element_lines = print_include_ref(include_element, sdf_root_dir)
lines.extend(indent_lines(element_lines, 6))
- for include_element in includes:
- element_lines = print_include_ref(include_element, sdf_root_dir)
- lines.extend(indent_lines(element_lines, 6))
-
- if elem_name != "plugin" and (len(elements) or len(includes)):
- lines.append(" ")
+ if elem_name != "plugin" and (len(elements) or len(includes)):
+ lines.append(" ")
- for attribute_element in attributes:
- lines.extend(indent_lines(print_attribute(attribute_element), 4))
+ for attribute in attributes:
+ lines.extend(indent_lines(print_attribute(attribute), 4))
lines.append(" ")
- lines.append("")
- else:
- if elem_type and is_std_type(elem_type):
- elem_type = f' type={xsd_type_string(elem_type)}'
- else:
- elem_type = ""
+ lines.append("")
- lines.append(f"")
return lines