ROS中調用第三方庫tinyxml2解析XML文件(以解析launch文件爲例)

1.TinyXML2的簡介

第三方庫TinyXML2是簡單、小型、高效、開源的C++ XML文件解析庫,可以很方便的應用到現有的項目之中,非常適合解析XML文件,存儲簡單數據,配置文件以及對象序列化等數據量不是很大的操作。
TinyXML2詳細介紹與詳見:TinyXML2官網

2.TinyXML2的獲取與安裝

TinyXML2可以通過其Github主頁獲取源代碼,具體來說可以採用git clone命令或直接下載其ZIP壓縮包,git clone命令的調用情況如下:

git clone https://github.com/leethomason/tinyxml2

在命令行進入tinyxml2目錄下,使用下列命令編譯並安裝TinyXML2,命令執行情況如下(其中的$符號爲shell下的命令提示符):

$ sudo make install
mkdir -p /usr/local
mkdir -p /usr/local/bin
mkdir -p /usr/local/lib
mkdir -p /usr/local/include
install xmltest /usr/local/bin/xmltest
install -m 644 tinyxml2.h /usr/local/include/tinyxml2.h
install -m 644 libtinyxml2.a /usr/local/lib/libtinyxml2.a

由此,便將tinyxml2庫安裝到了本地環境中,在tinyxml2目錄下執行如下命令,可以運行TinyXML2的測試代碼:

$ xmltest

該例程執行時用到的xml文件均位於tinyxml2目錄中的resources目錄下,該程序執行結束後的終端提示如下圖所示:
在這裏插入圖片描述
由此說明tinyxml2庫已經成功安裝完成。

2.TinyXML2的簡單使用示例

將TinyXML2安裝到本地環境之後,下面演示TinyXML2的簡單使用:
將TinyXML2源碼文件夾中的文件xmltest.cppresources/dream.xml複製到目標文件夾用於示例演示。在這裏插入圖片描述

這裏,在xmltest.c文件中引用了頭文件tinyxml2.h

#include "tinyxml2.h"

同時聲明瞭TinyXML2的命名空間:

using namespace tinyxml2;

另外,在main函數中定義:若程序的參數大於1,則創建XMLDocument對象並加載程序參數指定的文件,最後若成功加載文件,則輸出相關處理時間,該部分代碼如下所示。

	if ( argc > 1 ) {
		XMLDocument* doc = new XMLDocument();
		clock_t startTime = clock();
		doc->LoadFile( argv[1] );
 		clock_t loadTime = clock();
		int errorID = doc->ErrorID();
		delete doc; doc = 0;
 		clock_t deleteTime = clock();

		printf( "Test file '%s' loaded. ErrorID=%d\n", argv[1], errorID );
		if ( !errorID ) {
			printf( "Load time=%u\n",   (unsigned)(loadTime - startTime) );
			printf( "Delete time=%u\n", (unsigned)(deleteTime - loadTime) );
			printf( "Total time=%u\n",  (unsigned)(deleteTime - startTime) );
		}
		exit(0);
	}

使用如下命令編譯程序:

$ g++ -o xmltest xmltest.cpp -ltinyxml2

若出現找不到頭文件或找不到靜態庫的錯誤提示,還可以手動指定頭文件目錄(-I)和庫文件目錄(-L):

$ g++ -o xmltest xmltest.cpp -I /usr/local/include -L /usr/local/lib -ltinyxml2 

輸入如下命令,執行示例程序:

$ xmltest dream.xml

可以看到程序輸出如下,即程序成功加載了XML文件dream.xml。
在這裏插入圖片描述

3.TinyXML2在ROS中的使用

下面通過在ROS工作空間創建一個ROS功能包tinyxml_test使用TinyXML2解析ROS中的Launch文件,以此演示TnyXML2在ROS中的使用。
下面是該功能包的package.xml文件:

<?xml version="1.0"?>
<package format="2">
  <name>tinyxml_test</name>
  <version>0.0.0</version>
  <license>TODO</license>
  <maintainer email="[email protected]">jacky</maintainer>
  <description>The tinyxml_test package</description>
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>tf</build_depend>
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>tf</build_export_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>tf</exec_depend>
</package>

功能包中的Node源文件tinyxml_test_node.cpp代碼如下:

#include <ros/ros.h>
#include <ros/package.h>
#include <tf/transform_broadcaster.h>
#include <tinyxml2.h>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <map>

using namespace tinyxml2;//使用tinyxml2命名空間
using namespace std;

/* 功能函數 */
float String2Float(std::string numStr){
  std::stringstream ss(numStr);
  float num;
  ss>>num;
  return num;
}

int main(int argc, char **argv) {
  ros::init(argc, argv, "tinyxml_test_node");
  ros::NodeHandle nh("~"), nh_param("~");

  std::string tf_launch_file;
  //注:"test.launch"文件位於catkin_ws/src/tinyxml_test/launch/目錄下
  nh_param.param<std::string>("tf_launch_file", tf_launch_file,"test.launch");

  /*Load launch file to get the tf args.*/
  XMLDocument* doc = new XMLDocument();
  std::map<std::string,tf::StampedTransform> stampedTransformMap;//用於存儲多個不同座標系之間的轉換關係

  std::string filePath = ros::package::getPath("tinyxml_test");//獲取tinyxml_test功能包的絕對路徑
  filePath+="/launch/";
  filePath+=tf_launch_file;
  doc->LoadFile( filePath.c_str() );
  int errorID = doc->ErrorID();
  if(errorID){
      ROS_FATAL("[radar_freespace] Failed to load launch file %s.", filePath.c_str());
      return -1;
  }
  ROS_INFO( "[radar_freespace] Launch file '%s' loaded.", filePath.c_str());

  XMLElement* rootElement = doc->RootElement();//獲得根元素,即launch
  std::cout<<"Root Element:"<<rootElement->Value()<<std::endl;
  for(const XMLElement* element =rootElement->FirstChildElement();element;element=element->NextSiblingElement()){
    const XMLAttribute* argsAttr = element->FindAttribute("args");
    
    std::string argsAttrStr=std::string(argsAttr->Value());
    std::cout<<"Args Attribute:"<<argsAttrStr<<std::endl;
    std::stringstream ss(argsAttrStr);
    std::vector<std::string> argStrVec;
    std::string tmpArgStr = "";
    while (std::getline(ss, tmpArgStr, ' ')) {
      argStrVec.push_back(tmpArgStr);
    }
    tf::Transform transform;
    transform.setOrigin(tf::Vector3(String2Float(argStrVec.at(0)),String2Float(argStrVec.at(1)),String2Float(argStrVec.at(2))));
    tf::Quaternion quaternion;
    quaternion.setRPY(String2Float(argStrVec.at(5)),String2Float(argStrVec.at(4)),String2Float(argStrVec.at(3)));
    transform.setRotation(quaternion);
    stampedTransformMap[argStrVec.at(6)]=tf::StampedTransform(transform,ros::Time::now(),argStrVec.at(6),argStrVec.at(7));
	
	//輸出launch文件中記錄的座標系轉換關係
    std::cout<<stampedTransformMap[argStrVec.at(6)].getOrigin().x()<<","<<stampedTransformMap[argStrVec.at(6)].getOrigin().y()<<","<<stampedTransformMap[argStrVec.at(6)].getOrigin().z()<<",";
    std::cout<<stampedTransformMap[argStrVec.at(6)].getRotation().getW()<<","<<stampedTransformMap[argStrVec.at(6)].getRotation().getX()<<","<<stampedTransformMap[argStrVec.at(6)].getRotation().getY()<<","<<stampedTransformMap[argStrVec.at(6)].getRotation().getZ()<<std::endl;
  }
  
  delete doc; //刪除對象
  doc = NULL; //避免野指針

  ros::spin();
  return 0;
}

該Node能夠利用TinyXML2解析本功能包中的Launch文件(XML格式),該文件爲位於catkin_ws/src/tinyxml_test/launch/目錄下的文件test.launch,並且最後可以根據讀取的launch文件輸出其中保存的tf座標轉換信息。test.launch文件的內容如下:

<?xml version="1.0"?>
<launch>
  <node pkg="tf" type="static_transform_publisher" name="sensor_frame_to_world" args="2 2 2 0 0 0 world sensor_frame 100" />
  <node pkg="tf" type="static_transform_publisher" name="imu_to_sensor_frame" args="0 0 0 0 0 0 sensor_frame imu 100" />
  <node pkg="tf" type="static_transform_publisher" name="lidar_to_sensor_frame" args="1 1 1 0 0 0 sensor_frame lidar 100" />
</launch>

要編譯該功能包,還需要修改其CMakeLists.txt文件,添加必要的功能包、包含目錄、源代碼和鏈接庫等。該文件的完整內容如下:

cmake_minimum_required(VERSION 2.8.3)
project(tinyxml_test)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  roscpp
  roslib
  tf
)

###################################
## catkin specific configuration ##
###################################

catkin_package(
  LIBRARIES 
    ${PROJECT_NAME}
  CATKIN_DEPENDS
   roscpp tf
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
  ${catkin_INCLUDE_DIRS}
)

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
add_executable(${PROJECT_NAME}_node src/tinyxml_test_node.cpp)

## Specify libraries to link a library or executable target against
target_link_libraries(${PROJECT_NAME}_node
  -ltinyxml2
  ${catkin_LIBRARIES}
)

#############
## Install ##
#############

## Mark executables and/or libraries for installation
install(
  TARGETS 
    ${PROJECT_NAME}_node
  ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
  LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

需要注意的是:由於程序需要調用第三方庫TinyXML2,因此需要在CMakeLists.txt文件中的find_package選項中添加roslib功能包,並在target_link_libraries選項中指定鏈接該靜態庫(-ltinyxml2)。

最後,執行命令catkin_make將功能包編譯成功之後,使用如下命令運行節點(需提前運行roscore節點):

$ rosrun tinyxml_test tinyxml_test_node 

程序能夠輸出launch文件中保存的tf座標轉換信息:
在這裏插入圖片描述
大功告成!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章