diff --git a/.gitignore b/.gitignore index 08e5eba3e..fe7c191e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build build_* *.*.sw? .vscode +__pycache__ diff --git a/sdf/1.10/CMakeLists.txt b/sdf/1.10/CMakeLists.txt index 531202e38..55a906f71 100644 --- a/sdf/1.10/CMakeLists.txt +++ b/sdf/1.10/CMakeLists.txt @@ -73,8 +73,8 @@ foreach(FIL ${sdfs}) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.xsd" - COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/tools/xmlschema.rb - ARGS -s ${CMAKE_CURRENT_SOURCE_DIR} -i ${ABS_FIL} -o ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/xmlschema.py + ARGS --sdf-dir ${CMAKE_CURRENT_SOURCE_DIR} --input-file ${ABS_FIL} --output-dir ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ABS_FIL} COMMENT "Running xml schema compiler on ${FIL}" VERBATIM) diff --git a/sdf/1.5/CMakeLists.txt b/sdf/1.5/CMakeLists.txt index be630c5c4..68ceba50f 100644 --- a/sdf/1.5/CMakeLists.txt +++ b/sdf/1.5/CMakeLists.txt @@ -67,8 +67,8 @@ foreach(FIL ${sdfs}) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.xsd" - COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/tools/xmlschema.rb - ARGS -s ${CMAKE_CURRENT_SOURCE_DIR} -i ${ABS_FIL} -o ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${Pyhton3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/xmlschema.py + ARGS --sdf-dir ${CMAKE_CURRENT_SOURCE_DIR} --input-file ${ABS_FIL} --output-dir ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ABS_FIL} COMMENT "Running xml schema compiler on ${FIL}" VERBATIM) diff --git a/sdf/1.6/CMakeLists.txt b/sdf/1.6/CMakeLists.txt index 0ed72231d..ed41bf3b1 100644 --- a/sdf/1.6/CMakeLists.txt +++ b/sdf/1.6/CMakeLists.txt @@ -71,8 +71,8 @@ foreach(FIL ${sdfs}) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.xsd" - COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/tools/xmlschema.rb - ARGS -s ${CMAKE_CURRENT_SOURCE_DIR} -i ${ABS_FIL} -o ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/xmlschema.py + ARGS --sdf-dir ${CMAKE_CURRENT_SOURCE_DIR} --input-file ${ABS_FIL} --output-dir ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ABS_FIL} COMMENT "Running xml schema compiler on ${FIL}" VERBATIM) diff --git a/sdf/1.7/CMakeLists.txt b/sdf/1.7/CMakeLists.txt index ce971c310..51e6fe9af 100644 --- a/sdf/1.7/CMakeLists.txt +++ b/sdf/1.7/CMakeLists.txt @@ -72,8 +72,8 @@ foreach(FIL ${sdfs}) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.xsd" - COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/tools/xmlschema.rb - ARGS -s ${CMAKE_CURRENT_SOURCE_DIR} -i ${ABS_FIL} -o ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/xmlschema.py + ARGS --sdf-dir ${CMAKE_CURRENT_SOURCE_DIR} --input-file ${ABS_FIL} --output-dir ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ABS_FIL} COMMENT "Running xml schema compiler on ${FIL}" VERBATIM) diff --git a/sdf/1.8/CMakeLists.txt b/sdf/1.8/CMakeLists.txt index 162b2941a..ed7f874df 100644 --- a/sdf/1.8/CMakeLists.txt +++ b/sdf/1.8/CMakeLists.txt @@ -74,8 +74,8 @@ foreach(FIL ${sdfs}) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.xsd" - COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/tools/xmlschema.rb - ARGS -s ${CMAKE_CURRENT_SOURCE_DIR} -i ${ABS_FIL} -o ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/xmlschema.py + ARGS --sdf-dir ${CMAKE_CURRENT_SOURCE_DIR} --input-file ${ABS_FIL} --output-dir ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ABS_FIL} COMMENT "Running xml schema compiler on ${FIL}" VERBATIM) diff --git a/sdf/1.9/CMakeLists.txt b/sdf/1.9/CMakeLists.txt index 28ae14907..193f82c23 100644 --- a/sdf/1.9/CMakeLists.txt +++ b/sdf/1.9/CMakeLists.txt @@ -74,8 +74,8 @@ foreach(FIL ${sdfs}) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.xsd" - COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/tools/xmlschema.rb - ARGS -s ${CMAKE_CURRENT_SOURCE_DIR} -i ${ABS_FIL} -o ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/xmlschema.py + ARGS --sdf-dir ${CMAKE_CURRENT_SOURCE_DIR} --input-file ${ABS_FIL} --output-dir ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ABS_FIL} COMMENT "Running xml schema compiler on ${FIL}" VERBATIM) diff --git a/src/Types.cc b/src/Types.cc index 8c3ddb5e9..6e202b936 100644 --- a/src/Types.cc +++ b/src/Types.cc @@ -149,7 +149,7 @@ std::string JoinName( const std::string &internal::SdfScopeDelimiter() { static const gz::utils::NeverDestroyed delimiter{ - std::string()}; + kScopeDelimiter}; return delimiter.Access(); } } diff --git a/src/Types_TEST.cc b/src/Types_TEST.cc index 392dfb137..37b951739 100644 --- a/src/Types_TEST.cc +++ b/src/Types_TEST.cc @@ -120,6 +120,7 @@ TEST(Types, ErrorsOutputStream) EXPECT_EQ(expected, output.str()); } +///////////////////////////////////////////////// TEST(Types, SplitName) { { @@ -159,6 +160,7 @@ TEST(Types, SplitName) } } +///////////////////////////////////////////////// TEST(Types, JoinName) { { @@ -198,3 +200,11 @@ TEST(Types, JoinName) EXPECT_EQ(joinedName, ""); } } + +///////////////////////////////////////////////// +TEST(Types, ScopeDelimiters) +{ + GZ_UTILS_WARN_IGNORE__DEPRECATED_DECLARATION + EXPECT_EQ(sdf::kScopeDelimiter, sdf::kSdfScopeDelimiter); + GZ_UTILS_WARN_RESUME__DEPRECATED_DECLARATION +} diff --git a/tools/xmlschema.py b/tools/xmlschema.py new file mode 100755 index 000000000..b3518f9ec --- /dev/null +++ b/tools/xmlschema.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +# Copyright 2023 Open Source Robotics Foundation, Inc. +# +# 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. + +""" +Conversion script for SDF definitions to XML XSD files +""" + +from xml.etree import ElementTree + +import os + +from typing import List, Dict, Tuple, Optional + + +# Mapping between "type" values found in SDF files to the corresponding +# XSD standard datatypes as defined by https://www.w3.org/TR/xmlschema11-2/ +SDF_TYPES_TO_XSD_STD_TYPES = { + "bool": "boolean", + "char": "char", + "int": "int", + "double": "double", + "float": "float", + "string": "string", + "unsigned int": "unsignedInt", + "unsigned long": "unsignedLong", +} + +# Mapping between "required" values found in SDF files to the corresponding +# minOccurs and maxOccurs found in XSD +SDF_REQUIRED_TO_MIN_MAX_OCCURS: Dict[str, Tuple[str, str]] = { + "0": ("0", "1"), # Required: 0, (minOccurs: 0, maxOccurs: 1) + "1": ("1", "1"), # Required: 1, (minOccurs: 1, maxOccurs: 1) + "+": ("1", "unbounded"), # Required: +, (minOccurs: 1, maxOccurs: inf) + "*": ("0", "unbounded"), # Required: *, (minOccurs: 0, maxOccurs: inf) + "-1": ("0", "unbounded"), # Required: -1, (minOccurs: 0, maxOccurs: inf) +} + + +def indent_lines(lines: List[str], indent: int) -> List[str]: + """ + Indent a list of xml lines group of lines by a number (indent) of spaces + + """ + return [" " * indent + line for line in lines] + + +def get_attribute(element: ElementTree.Element, attrib: str) -> Optional[str]: + """ + Retrieve XML attribute from an element + """ + return element.attrib[attrib] if attrib in element.attrib else None + + +def is_std_type(sdf_type: str) -> bool: + """ + Check if sdf_type is a known XSD standard type. + Return true if the sdf_type is in the set of known types, false otherwise. + """ + return sdf_type in SDF_TYPES_TO_XSD_STD_TYPES + + +def xsd_type_string(sdf_type: str) -> Optional[str]: + """ + Check if xsd_type is a known XSD standard type. + If it is, return 'xsd:' + type, None otherwise. + """ + if is_std_type(sdf_type): + xsd_type = SDF_TYPES_TO_XSD_STD_TYPES[sdf_type] + return "xsd:" + xsd_type + return None + + +def print_documentation(element: ElementTree.Element) -> List[str]: + """ + Print the documentation associated with an element + """ + lines = [] + description = element.find("description") + if ( + description is not None + and description.text is not None + and len(description.text) + ): + lines.append("") + lines.append(" ") + lines.append(f" ") + lines.append(" ") + lines.append("") + return lines + + +def print_include(element: ElementTree.Element) -> List[str]: + """ + Print include tag information + """ + lines = [] + filename = get_attribute(element, "filename") + if filename is not None: + loc = "http://sdformat.org/schemas/" + loc += filename.replace(".sdf", ".xsd") + lines.append(f"") + return lines + + +def print_include_ref(element: ElementTree.Element, sdf_root_dir: str) -> List[str]: + """ + Print include tag reference information + """ + lines = [] + filename = get_attribute(element, "filename") + if filename is not None: + sdf_path = os.path.join(sdf_root_dir, filename) + if not os.path.exists(sdf_path): + raise RuntimeError(f"Attempted to include non-existent file: {sdf_path}") + + include_tree = ElementTree.parse(sdf_path) + root = include_tree.getroot() + include_element_name = root.attrib["name"] + lines.append(f"") + return lines + + +def print_plugin_element(element: ElementTree.Element) -> List[str]: + """ + Separate handling of the 'plugin' element + """ + lines = [] + # Short circuit for plugin.sdf copy_data element + if "copy_data" in element.attrib: + lines.append("") + lines.append( + " " + ) + lines.append("") + return lines + + +def print_element(element: ElementTree.Element) -> List[str]: + """ + Print a child element of the sdf definition + """ + lines = [] + + elem_name = get_attribute(element, "name") + elem_type = get_attribute(element, "type") + elem_reqd = get_attribute(element, "required") + + if elem_type and is_std_type(elem_type): + elem_type = xsd_type_string(elem_type) + + 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: + lines.append(f"") + lines.extend(indent_lines(print_documentation(element), 2)) + lines.append(" ") + lines.append(" ") + + for child_element in element.findall("element"): + lines.extend(indent_lines(print_element(child_element), 6)) + + lines.append(" ") + + for attribute in element.findall("attribute"): + 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("") + return lines + + +def print_attribute(element: ElementTree.Element) -> List[str]: + """ + Print an attribute of the sdf definition + """ + lines = [] + + elem_name = get_attribute(element, "name") + elem_type = get_attribute(element, "type") + elem_reqd = get_attribute(element, "required") + elem_default = get_attribute(element, "default") + + if elem_type and is_std_type(elem_type): + elem_type = xsd_type_string(elem_type) + + use = "" + default = "" + + if elem_reqd == "1": + use = "use='required'" + elif elem_reqd == "0": + use = "use='optional'" + if elem_default is not None: + default = f"default='{elem_default}'" + + lines.append( + f"" + ) + lines.extend(indent_lines(print_documentation(element), 2)) + lines.append("") + return lines + + +def print_xsd(element: ElementTree.Element, sdf_root_dir: str) -> List[str]: + """ + Print xsd for top level SDF element + """ + lines = [] + + elem_name = get_attribute(element, "name") + elem_type = get_attribute(element, "type") + + elements = element.findall("element") + attributes = element.findall("attribute") + includes = element.findall("include") + + lines.extend(print_documentation(element)) + lines.append( + "" + ) + + # Reference any includes in the SDF file + for include in includes: + lines.extend(print_include(include)) + + if len(elements) or len(attributes) or len(includes): + lines.append(f"") + lines.append(" ") + + 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) + 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(" ") + + for attribute_element in attributes: + lines.extend(indent_lines(print_attribute(attribute_element), 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(f"") + return lines + + +def process(input_file_sdf: str, sdf_dir: str) -> List[str]: + """ + Produce an XSD file from an input SDF file + """ + lines = [] + tree = ElementTree.parse(input_file_sdf) + root = tree.getroot() + lines.append("") + lines.append("") + lines.extend(indent_lines(print_xsd(root, sdf_dir), 2)) + lines.append("") + return lines + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser("xmlschema.py") + parser.add_argument("--input-file") + parser.add_argument("--sdf-dir") + parser.add_argument("--output-dir") + args = parser.parse_args() + + input_file = os.path.abspath(args.input_file) + output_lines = process(input_file, args.sdf_dir) + fname = os.path.splitext(os.path.basename(args.input_file))[0] + os.makedirs(args.output_dir, exist_ok=True) + + output_file = os.path.join(args.output_dir, f"{fname}.xsd") + + with open(output_file, "w", encoding="utf8") as f: + f.write("\n".join(output_lines)) + f.write("\n") diff --git a/tools/xmlschema.rb b/tools/xmlschema.rb deleted file mode 100755 index cadfa59fb..000000000 --- a/tools/xmlschema.rb +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env ruby - -require "rexml/document" -require "optparse" - -$path = nil - -################################################# -# \brief A not very elegant way to convert to schema types -def xsdType(_type) - if _type == "unsigned int" - return "unsignedInt" - elsif _type == "unsigned long" - return "unsignedLog" - elsif _type == "bool" - return "boolean" - else - return _type - end -end - -################################################# -def isStdType(_type) - return _type == "string" || _type == "int" || _type == "double" || - _type == "float" || _type == "bool" || _type == "char" || - _type == "unsigned int" -end - -################################################# -def printElem(_file, _spaces, _elem) - - # this currently short-circuits the plugin.sdf copy_data element. - if _elem.attributes["name"].nil? - _file.printf("%*s\n", _spaces-2, "") - _file.printf("%*s\n", _spaces, "") - _file.printf("%*s\n", _spaces-2, "") - return - end - - type = _elem.attributes["type"] - if isStdType(type) - type = "xsd:" + xsdType(type) - end - - minOccurs = '0' - maxOccurs = 'unbounded' - if _elem.attributes["required"] == '0' - minOccurs='0' - maxOccurs='1' - elsif _elem.attributes["required"] == '1' - minOccurs='1' - maxOccurs='1' - elsif _elem.attributes["required"] == '+' - minOccurs='1' - maxOccurs='unbounded' - elsif _elem.attributes["required"] == '*' - minOccurs='0' - maxOccurs='unbounded' - end - - _file.printf("%*s\n", - _spaces, "", minOccurs, maxOccurs) - - # Print the complex type with a name - if type.nil? || type == "" - - _file.printf("%*s\n", - _spaces, "", _elem.attributes["name"]) - - if !_elem.elements["description"].nil? && - !_elem.elements["description"].text.nil? - printDocumentation(_file, _spaces+2, _elem.elements["description"].text) - end - - _file.printf("%*s\n", _spaces+2, "") - - _file.printf("%*s\n", _spaces+4, "") - - _elem.get_elements("element").each do |elem| - printElem(_file, _spaces+6, elem) - end - _file.printf("%*s\n", _spaces+4, "") - - # Print the attributes for the complex type - # Attributes must go at the end of the complex type. - _elem.get_elements("attribute").each do |attr| - printAttribute(_file, _spaces+4, attr); - end - - _file.printf("%*s\n", _spaces+2, "") - else - _file.printf("%*s\n", - _spaces, "", _elem.attributes["name"], type) - - if !_elem.elements["description"].nil? && - !_elem.elements["description"].text.nil? - printDocumentation(_file, _spaces+2, _elem.elements["description"].text) - end - end - - _file.printf("%*s\n", _spaces, "") - _file.printf("%*s\n", _spaces, "") - -end - -################################################# -def printDocumentation(_file, _spaces, _doc) - _file.printf("%*s\n", _spaces, "") - - _spaces += 2 - _file.printf("%*s\n", _spaces, "") - - _spaces += 2 - _file.printf("%*s\n",_spaces, "", _doc); - _spaces -= 2 - - _file.printf("%*s\n", _spaces, "") - _spaces -= 2 - - _file.printf("%*s\n", _spaces, "") -end - -################################################# -def printIncludeRef(_file, _spaces, _inc) - path = File.join($path, _inc.attributes["filename"]) - doc = REXML::Document.new File.new(path) - incElemName = doc.root.attributes['name'] - _file.printf("%*s\n", _spaces, "", incElemName) -end - -################################################# -def printInclude(_file, _spaces, _attr) - loc = "http://sdformat.org/schemas/" - loc += _attr.attributes['filename'].sub("\.sdf","\.xsd") - _file.printf("%*s\n", _spaces, "", loc) -end - -################################################# -def printAttribute(_file, _spaces, _attr) - name = _attr.attributes["name"] - type = _attr.attributes["type"] - use = "" - default = "" - - if !_attr.attributes["required"].nil? - if _attr.attributes["required"] == "1" - use = "use='required'" - elsif _attr.attributes["required"] == "0" - use = "use='optional'" - - # Default is only valid if use is optional - if !_attr.attributes["default"].nil? - default="default='#{_attr.attributes["default"]}'" - end - end - end - - if isStdType(type) - type = "xsd:" + xsdType(type) - end - - _file.printf("%*s\n", _spaces, - "", name, type, use, default) - - if !_attr.elements["description"].nil? && - !_attr.elements["description"].text.nil? - printDocumentation(_file, _spaces+2, _attr.elements["description"].text) - end - _file.printf("%*s\n", _spaces, "") -end - -################################################# -# \brief Print the complete schema for an element into a file. -# \param[in] _file File pointer in which to print the schema. -# \param[in] _spaces Number of spaces to prepend to each line. -# \param[in] _elem The SDF element to convert to an xml schema. -def printXSD(_file, _spaces, _elem) - - if !_elem.elements["description"].nil? && - !_elem.elements["description"].text.nil? - printDocumentation(_file, _spaces, _elem.elements["description"].text) - end - - _file.printf("%*s\n", _spaces, "") - - # Print the inclues for the complex type - # The includes must appear first - _elem.get_elements("include").each do |inc| - printInclude(_file, _spaces, inc); - end - - if _elem.get_elements("element").size > 0 || - _elem.get_elements("attribute").size > 0 || - _elem.get_elements("include").size > 0 - - # Print the complex type with a name - _file.printf("%*s\n", _spaces, "", - _elem.attributes["name"]) - _file.printf("%*s\n", _spaces+2, "") - - if _elem.attributes['name'] != "plugin" && - (_elem.get_elements("element").size > 0 || - _elem.get_elements("include").size > 0) - _file.printf("%*s\n", _spaces+4, "") - end - - # Print all the child elements - _elem.get_elements("element").each do |elem| - printElem(_file, _spaces+6, elem); - end - - # Print all the included sdf's root elements - _elem.get_elements("include").each do |inc| - printIncludeRef(_file, _spaces+6, inc); - end - if _elem.attributes['name'] != "plugin" && - (_elem.get_elements("element").size > 0 || - _elem.get_elements("include").size > 0) - _file.printf("%*s\n", _spaces+4, "") - end - - # Print the attributes for the complex type - # Attributes must go at the end of the complex type. - _elem.get_elements("attribute").each do |attr| - printAttribute(_file, _spaces+4, attr); - end - - # Close the complex type - _file.printf("%*s\n", _spaces+2, "") - _file.printf("%*s\n", _spaces, "") - else - type = _elem.attributes["type"] - - if isStdType(type) - type = "xsd:" + type - end - - if !type.nil? - type = "type='" + type + "'" - end - - _file.printf("%*s\n", _spaces, "", - _elem.attributes["name"], type) - end -end - - -infile = nil -outdir = nil - -opt_parser = OptionParser.new do |o| - o.on("-i", "--in [path]", String, - "SDF file to compile") {|path| infile = path} - o.on("-o", "--out [path]", String, - "Output directory for source and header files") {|path| outdir = path} - o.on("-s", "--sdf [path]", String, - "Directory containing all the SDF files") {|path| $path = path} - o.on("-h", "--help", "Display this help message") do - puts opt_parser - exit - end -end -opt_parser.parse! - -if infile.nil? - puts "Missing option -i." - exit -elsif !File.exist?(infile) - puts "Input file[#{infile}] does not exist\n" - exit -end - -if $path.nil? - puts "Missing option -s." - exit -elsif !Dir.exist?($path) - puts "SDF source dir[#{$path}] does not exist\n" - exit -end - -if outdir.nil? - puts "Missing output directory, option -o." - exit -elsif !Dir.exist?(outdir) - Dir.mkdir(outdir) -end - -doc = REXML::Document.new File.new(infile) - -spaces = 2 -doc.elements.each_with_index("element") do |elem, i| - out_xsd = infile.split("/").last.sub("\.sdf","\.xsd") - file = File.open(File.join(outdir, out_xsd), "w") - - file.print("\n") - file.print("\n") - - printXSD(file, spaces, elem) - - file.print("\n") - file.close() -end