Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sdf/1.12/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions sdf/catalog.xml.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE catalog PUBLIC "-//OASIS//DTD XML Catalogs V1.0//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/catalog.xml">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<rewriteURI uriStartString="http://sdformat.org/schemas/" rewritePrefix="file://@CMAKE_CURRENT_BINARY_DIR@/"/>
</catalog>
27 changes: 2 additions & 25 deletions test/integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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})
Expand Down
8 changes: 6 additions & 2 deletions test/integration/schema_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#include <gtest/gtest.h>

#include <gz/utils/Environment.hh>

#include "sdf/parser.hh"

#include "test_config.hh"
Expand All @@ -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)
Expand Down
134 changes: 93 additions & 41 deletions tools/xmlschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"<xsd:element ref='{include_element_name}'/>")

elem_reqd = get_attribute(element, "required")
if elem_reqd:
min_occurs, max_occurs = SDF_REQUIRED_TO_MIN_MAX_OCCURS[elem_reqd]
lines.append(f"<xsd:choice minOccurs='{min_occurs}' maxOccurs='{max_occurs}'>")
lines.append(f" <xsd:element ref='{include_element_name}'/>")
lines.append("</xsd:choice>")
else:
lines.append(f"<xsd:element ref='{include_element_name}'/>")
return lines


Expand All @@ -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
"""
Expand All @@ -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"<xsd:choice minOccurs='{min_occurs}' maxOccurs='{max_occurs}'>")

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"<xsd:element name='{elem_name}' type='{elem_type}'>")
else:
lines.append(f"<xsd:element name='{elem_name}'>")
lines.extend(indent_lines(print_documentation(element), 2))

lines.extend(indent_lines(print_documentation(element), 2))

if complex_type:
lines.append(" <xsd:complexType>")
lines.append(" <xsd:choice maxOccurs='unbounded'>")

for child_element in element.findall("element"):
lines.extend(indent_lines(print_element(child_element), 6))
if elem_type:
lines.append(" <xsd:simpleContent>")
lines.append(f" <xsd:extension base='{elem_type}'>")
for attribute in attributes:
lines.extend(indent_lines(print_attribute(attribute), 8))
lines.append(" </xsd:extension>")
lines.append(" </xsd:simpleContent>")
else:
if len(elements) or len(includes):
lines.append(" <xsd:choice maxOccurs='unbounded'>")

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(" </xsd:choice>")
if len(elements) or len(includes):
lines.append(" </xsd:choice>")

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(" </xsd:complexType>")
else:
lines.append(f"<xsd:element name='{elem_name}' type='{elem_type}'>")
lines.extend(indent_lines(print_documentation(element), 2))

lines.append("</xsd:element>")
lines.append("</xsd:choice>")
Expand Down Expand Up @@ -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")
Expand All @@ -240,44 +279,57 @@ def print_xsd(element: ElementTree.Element, sdf_root_dir: str) -> List[str]:
"<xsd:include schemaLocation='http://sdformat.org/schemas/types.xsd'/>"
)

# 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"<xsd:element name='{elem_name}' type='{elem_type}'>")
else:
lines.append(f"<xsd:element name='{elem_name}'>")
lines.append(" <xsd:complexType>")

if elem_name != "plugin" and (len(elements) or len(includes)):
lines.append(" <xsd:choice maxOccurs='unbounded'>")
if complex_type:
lines.append(" <xsd:complexType>")

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(" <xsd:simpleContent>")
lines.append(f" <xsd:extension base='{elem_type}'>")
for attribute in attributes:
lines.extend(indent_lines(print_attribute(attribute), 8))
lines.append(" </xsd:extension>")
lines.append(" </xsd:simpleContent>")
else:
if elem_name != "plugin" and (len(elements) or len(includes)):
lines.append(" <xsd:choice maxOccurs='unbounded'>")

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(" </xsd:choice>")
if elem_name != "plugin" and (len(elements) or len(includes)):
lines.append(" </xsd:choice>")

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(" </xsd:complexType>")
lines.append("</xsd:element>")
else:
if elem_type and is_std_type(elem_type):
elem_type = f' type={xsd_type_string(elem_type)}'
else:
elem_type = ""
lines.append("</xsd:element>")

lines.append(f"<xsd:element name='{elem_name}'{elem_type} />")
return lines


Expand Down
Loading