diff --git a/sdf/CMakeLists.txt b/sdf/CMakeLists.txt index 90a27c4ad..a7a9d8c12 100644 --- a/sdf/CMakeLists.txt +++ b/sdf/CMakeLists.txt @@ -10,12 +10,12 @@ add_subdirectory(1.8) add_custom_target(schema) add_dependencies(schema schema1_8) -# Generate the EmbeddedSdf.hh file, which contains all the supported SDF +# Generate the EmbeddedSdf.cc file, which contains all the supported SDF # descriptions in a map of strings. The parser.cc file uses EmbeddedSdf.hh. execute_process( COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.rb WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf" - OUTPUT_FILE "${PROJECT_BINARY_DIR}/include/sdf/EmbeddedSdf.hh" + OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc" ) # Generate aggregated SDF description files for use by the sdformat.org diff --git a/sdf/embedSdf.rb b/sdf/embedSdf.rb index 40beff053..c05acac26 100755 --- a/sdf/embedSdf.rb +++ b/sdf/embedSdf.rb @@ -9,62 +9,37 @@ supportedSdfConversions = ['1.8', '1.7', '1.6', '1.5', '1.4', '1.3'] puts %q! -#ifndef SDF_INTERNAL_EMBEDDEDSDF_HH_ -#define SDF_INTERNAL_EMBEDDEDSDF_HH_ +#include "EmbeddedSdf.hh" -// An empty SDF string is returned if a query into the embeddedSdf map fails. -static const std::string emptySdfString = ""; +namespace sdf { +inline namespace SDF_VERSION_NAMESPACE { -// A map of maps where the keys in the first/parent map are SDF version -// strings, keys in the second/child map are SDF specification filenames and -// values are the contents of the SDF specification files. -static const std::map> embeddedSdf = { +const std::map &GetEmbeddedSdf() { + static const std::map result{ ! -# Iterate over each version -supportedSdfVersions.each do |version| - # Make sure the directory exists. Quietly fail so that we don't pollute - # the output, which gets included in EmbeddedSdf.hh - if Dir.exist?(version) - puts "{\"#{version}\", {" - - # Iterate over each .sdf file in the version directory - Dir.glob("#{version}/*.sdf") do |file| - - # Store the contents of the file in the child map - puts "{\"#{File.basename(file)}\", R\"__sdf_literal__(" - infile = File.open(file) - puts infile.read - puts ")__sdf_literal__\"}," - end - puts "}}," - end +# Stores the contents of the file in the map. +def embed(pathname) + puts "{\"#{pathname}\", R\"__sdf_literal__(" + infile = File.open(pathname) + puts infile.read + puts ")__sdf_literal__\"}," end -puts "};" - -puts "static const std::map> conversionMap = {" +# Embed the supported *.sdf files. +supportedSdfVersions.each do |version| + Dir.glob("#{version}/*.sdf").sort.each { |file| embed(file) } +end -# Iterate over each version +# Embed the supported *.convert files. supportedSdfConversions.each do |version| - # from-to - # Make sure the directory exists. Quietly fail so that we don't pollute - # the output, which gets included in EmbeddedSdf.hh - if Dir.exist?(version) - - # Iterate over each .sdf file in the version directory - Dir.glob("#{version}/*.convert") do |file| - - basename = File.basename(file, ".*").gsub(/_/, '.') - # Store the contents of the file in the child map - puts "{\"#{basename}\", {\"#{version}\", R\"__sdf_literal__(" - infile = File.open(file) - puts infile.read - puts ")__sdf_literal__\"}}," - end - end + Dir.glob("#{version}/*.convert").sort.each { |file| embed(file) } end puts %q! -}; -#endif + }; + return result; +} + +} +} ! diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 313883004..ea2e68e0f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,7 @@ set (sources Converter.cc Cylinder.cc Element.cc + EmbeddedSdf.cc Error.cc Exception.cc Frame.cc @@ -62,6 +63,7 @@ set (sources Visual.cc World.cc ) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (USE_EXTERNAL_TINYXML) include_directories(${tinyxml_INCLUDE_DIRS}) @@ -157,7 +159,7 @@ if (NOT WIN32) endif() if (NOT WIN32) - set(SDF_BUILD_TESTS_EXTRA_EXE_SRCS Converter.cc) + set(SDF_BUILD_TESTS_EXTRA_EXE_SRCS Converter.cc EmbeddedSdf.cc) sdf_build_tests(Converter_TEST.cc) endif() diff --git a/src/Converter.cc b/src/Converter.cc index 4e5abcbfb..4dc66a1d7 100644 --- a/src/Converter.cc +++ b/src/Converter.cc @@ -29,12 +29,18 @@ #include "sdf/Types.hh" #include "Converter.hh" - -// This include file is generated at configure time. -#include "sdf/EmbeddedSdf.hh" +#include "EmbeddedSdf.hh" using namespace sdf; +namespace { +bool EndsWith(const std::string& _a, const std::string& _b) +{ + return (_a.size() >= _b.size()) && + (_a.compare(_a.size() - _b.size(), _b.size(), _b) == 0); +} +} + ///////////////////////////////////////////////// bool Converter::Convert(TiXmlDocument *_doc, const std::string &_toVersion, bool _quiet) @@ -73,28 +79,36 @@ bool Converter::Convert(TiXmlDocument *_doc, const std::string &_toVersion, elem->SetAttribute("version", _toVersion); - // The conversionMap in EmbeddedSdf.hh has keys that represent a version - // of SDF to convert from. The values in conversionmap are pairs, where - // the first element is the SDF version that the second element will - // convert to. For example, the following will convert from 1.4 to 1.5 - // according to "conversion_xml": - // - // {"1.4", {"1.5", "conversion_xml"}} - std::map >::const_iterator - fromIter = conversionMap.find(origVersion); - - std::string toVer = ""; + // The conversion recipes within the embedded files database are named, e.g., + // "1.8/1_7.convert" to upgrade from 1.7 to 1.8. + const std::map &embedded = GetEmbeddedSdf(); - // Starting with the original SDF version, perform all the conversions - // necessary in order to reach the _toVersion. - while (fromIter != conversionMap.end() && fromIter->first != _toVersion) + // Apply the conversions one at a time until we reach the desired _toVersion. + std::string curVersion = origVersion; + while (curVersion != _toVersion) { - // Get the SDF to version. - toVer = fromIter->second.first; + // Find the (at most one) file named, e.g., ".../1_7.convert". + std::string snakeVersion = curVersion; + std::replace(snakeVersion.begin(), snakeVersion.end(), '.', '_'); + const std::string suffix = "/" + snakeVersion + ".convert"; + const char* convertXml = nullptr; + for (const auto& [pathname, data] : embedded) + { + if (EndsWith(pathname, suffix)) + { + curVersion = pathname.substr(0, pathname.size() - suffix.size()); + convertXml = data.c_str(); + break; + } + } + if (convertXml == nullptr) + { + break; + } // Parse and apply the conversion XML. TiXmlDocument xmlDoc; - xmlDoc.Parse(fromIter->second.second.c_str()); + xmlDoc.Parse(convertXml); if (xmlDoc.Error()) { sdferr << "Error parsing XML from string: " @@ -102,13 +116,10 @@ bool Converter::Convert(TiXmlDocument *_doc, const std::string &_toVersion, return false; } ConvertImpl(elem, xmlDoc.FirstChildElement("convert")); - - // Get the next conversion XML map element. - fromIter = conversionMap.find(toVer); } // Check that we actually converted to the desired final version. - if (toVer != _toVersion) + if (curVersion != _toVersion) { sdferr << "Unable to convert from SDF version " << origVersion << " to " << _toVersion << "\n"; diff --git a/src/EmbeddedSdf.hh b/src/EmbeddedSdf.hh new file mode 100755 index 000000000..b0c447b14 --- /dev/null +++ b/src/EmbeddedSdf.hh @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Open Source Robotics Foundation + * + * 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 SDF_EMBEDDEDSDF_HH_ +#define SDF_EMBEDDEDSDF_HH_ + +#include +#include + +#include "sdf/Types.hh" + +namespace sdf +{ + // Inline bracket to help doxygen filtering. + inline namespace SDF_VERSION_NAMESPACE { + // + + /// \internal + + /// A map where the keys are a source-relative pathnames within the "sdf" + /// directory such as "1.8/root.sdf", and the values are the contents of + /// that source file. + const std::map &GetEmbeddedSdf(); +} +} +#endif diff --git a/src/SDF.cc b/src/SDF.cc index 48c1943b3..20dcd4c67 100644 --- a/src/SDF.cc +++ b/src/SDF.cc @@ -31,9 +31,7 @@ #include "sdf/SDFImpl.hh" #include "SDFImplPrivate.hh" #include "sdf/sdf_config.h" - -// This include file is generated at configure time. -#include "sdf/EmbeddedSdf.hh" +#include "EmbeddedSdf.hh" namespace sdf { @@ -453,7 +451,8 @@ const std::string &SDF::EmbeddedSpec( { try { - return embeddedSdf.at(SDF::Version()).at(_filename); + const std::string pathname = SDF::Version() + "/" + _filename; + return GetEmbeddedSdf().at(pathname); } catch(const std::out_of_range &) { @@ -461,6 +460,9 @@ const std::string &SDF::EmbeddedSpec( sdferr << "Unable to find SDF filename[" << _filename << "] with " << "version " << SDF::Version() << "\n"; } + + // An empty SDF string is returned if a query into the embeddedSdf map fails. + static const std::string emptySdfString; return emptySdfString; } }