├── MANIFEST.in ├── .gitignore ├── xacro4sdf ├── __init__.py ├── xml_format.py ├── common.xmacro └── xacro4sdf.py ├── test ├── test_custom_usage.py ├── model3.sdf.xmacro ├── model2.sdf.xmacro ├── model.sdf.xmacro ├── model2.sdf ├── model.sdf ├── model3.sdf └── custom.sdf ├── setup.py ├── .github └── workflows │ └── python-publish.yml ├── LICENSE └── README.md /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include xacro4sdf/common.xmacro -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | xacro4sdf.egg-info/ 4 | *.pyc 5 | -------------------------------------------------------------------------------- /xacro4sdf/__init__.py: -------------------------------------------------------------------------------- 1 | from xacro4sdf.xacro4sdf import xacro4sdf_main -------------------------------------------------------------------------------- /test/test_custom_usage.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | from xacro4sdf.xacro4sdf import XMLMacro 3 | 4 | xmacro=XMLMacro() 5 | #case1 parse from file 6 | xmacro.set_xml_file("model.sdf.xmacro") 7 | custom_property={"rplidar_a2_h":0.8} 8 | xmacro.generate(custom_property) 9 | xmacro.set_static(True) 10 | xmacro.to_file("custom.sdf",banner_info="test_custom_usage.py") -------------------------------------------------------------------------------- /test/model3.sdf.xmacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 0 0.03 0 0 0 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/model2.sdf.xmacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 0 0.02 0 0 0 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from setuptools import setup, find_packages 4 | 5 | from os import path 6 | this_directory = path.abspath(path.dirname(__file__)) 7 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: 8 | long_description = f.read() 9 | 10 | setup( 11 | name='xacro4sdf', 12 | version='2.1.0', 13 | author='Zhenpeng Ge', 14 | author_email='zhenpeng.ge@qq.com', 15 | url='https://github.com/gezp/xacro4sdf', 16 | description='a simple XML macro script for sdf, like ros/xacro which is desiged for urdf.', 17 | long_description=long_description, 18 | long_description_content_type='text/markdown', 19 | license='MIT', 20 | keywords=['sdf','sdformat','xacro', 'gazebo', 'ignition'], 21 | include_package_data=True, 22 | packages=find_packages(), 23 | entry_points={ 24 | 'console_scripts': [ 25 | 'xacro4sdf=xacro4sdf:xacro4sdf_main', 26 | ] 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /test/model.sdf.xmacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 0 0 0.02 0 0 0 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 gezp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/model2.sdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 0 0 0.02 0 0 0 12 | 0.5 13 | 14 | 0.037083333333333336 15 | 0 16 | 0 17 | 0.03333333333333334 18 | 0 19 | 0.017083333333333336 20 | 21 | 22 | 23 | 24 | 25 | model://rplidar_a2/meshes/rplidar_a2.dae 26 | 27 | 28 | 29 | 30 | 31 | 32 | model://rplidar_a2/meshes/rplidar_a2.dae 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/model.sdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 0 0 0.02 0 0 0 12 | 0.5 13 | 14 | 0.01041666666666667 15 | 0 16 | 0 17 | 0.008333333333333335 18 | 0 19 | 0.005416666666666668 20 | 21 | 22 | 23 | 24 | 25 | model://rplidar_a2/meshes/rplidar_a2.dae 26 | 27 | 28 | 29 | 30 | 31 | 32 | model://rplidar_a2/meshes/rplidar_a2.dae 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/model3.sdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 0 0 0.03 0 0 0 12 | 0.5 13 | 14 | 0.037083333333333336 15 | 0 16 | 0 17 | 0.03333333333333334 18 | 0 19 | 0.017083333333333336 20 | 21 | 22 | 23 | 24 | 25 | model://rplidar_a2/meshes/rplidar_a2.dae 26 | 27 | 28 | 29 | 30 | 31 | 32 | model://rplidar_a2/meshes/rplidar_a2.dae 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/custom.sdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 0 0 0.02 0 0 0 13 | 0.5 14 | 15 | 0.1404166666666667 16 | 0 17 | 0 18 | 0.13333333333333336 19 | 0 20 | 0.060416666666666674 21 | 22 | 23 | 24 | 25 | 26 | model://rplidar_a2/meshes/rplidar_a2.dae 27 | 28 | 29 | 30 | 31 | 32 | 33 | model://rplidar_a2/meshes/rplidar_a2.dae 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /xacro4sdf/xml_format.py: -------------------------------------------------------------------------------- 1 | import xml.dom.minidom 2 | 3 | ########################################################################################################### 4 | # Better pretty printing of xml 5 | # Taken from http://ronrothman.com/public/leftbraned/xml-dom-minidom-toprettyxml-and-silly-whitespace/ 6 | 7 | def fixed_writexml(self, writer, indent="", addindent="", newl=""): 8 | # indent = current indentation 9 | # addindent = indentation to add to higher levels 10 | # newl = newline string 11 | writer.write(indent + "<" + self.tagName) 12 | 13 | attrs = self._get_attributes() 14 | a_names = sorted(attrs.keys()) 15 | 16 | for a_name in a_names: 17 | writer.write(" %s=\"" % a_name) 18 | xml.dom.minidom._write_data(writer, attrs[a_name].value) 19 | writer.write("\"") 20 | if self.childNodes: 21 | if len(self.childNodes) == 1 \ 22 | and self.childNodes[0].nodeType == xml.dom.minidom.Node.TEXT_NODE: 23 | writer.write(">") 24 | self.childNodes[0].writexml(writer, "", "", "") 25 | writer.write("%s" % (self.tagName, newl)) 26 | return 27 | writer.write(">%s" % newl) 28 | for node in self.childNodes: 29 | # skip whitespace-only text nodes 30 | if node.nodeType == xml.dom.minidom.Node.TEXT_NODE and \ 31 | (not node.data or node.data.isspace()): 32 | continue 33 | node.writexml(writer, indent + addindent, addindent, newl) 34 | writer.write("%s%s" % (indent, self.tagName, newl)) 35 | else: 36 | writer.write("/>%s" % newl) 37 | 38 | # replace minidom's function with ours 39 | xml.dom.minidom.Element.writexml = fixed_writexml 40 | ################################################################################################### 41 | -------------------------------------------------------------------------------- /xacro4sdf/common.xmacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${m} 6 | 7 | ${m*(3*r*r+l*l)/12} 8 | 0 9 | 0 10 | ${m*(3*r*r+l*l)/12} 11 | 0 12 | ${m*r*r/2} 13 | 14 | 15 | 16 | 17 | ${m} 18 | 19 | ${m*(y*y+z*z)/12} 20 | 0 21 | 0 22 | ${m*(x*x+z*z)/12} 23 | 0 24 | ${m*(x*x+y*y)/12} 25 | 26 | 27 | 28 | 29 | ${m} 30 | 31 | ${2*m*r*r/5} 32 | 0 33 | 0 34 | ${2*m*r*r/5} 35 | 0 36 | ${2*m*r*r/5} 37 | 38 | 39 | 40 | 41 | 42 | 43 | ${r} 44 | ${l} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ${x} ${y} ${z} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ${r} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ${uri} 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ${uri} 78 | 79 | 80 | 81 | 82 | 83 | 84 | ${uri} 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xacro4sdf 2 | 3 | ![PyPI](https://img.shields.io/pypi/v/xacro4sdf) ![](https://img.shields.io/pypi/l/xacro4sdf) ![](https://img.shields.io/pypi/dm/xacro4sdf) 4 | 5 | **[xmacro](https://github.com/gezp/xmacro) is recommended to replace `xacro4sdf`, which is a refactor version of `xacro4sdf`. and `xacro4sdf` won't be maintained in the future.** 6 | 7 | `xacro4sdf` is a simple tool to define and parse XML macro for [sdf (sdformat)](http://sdformat.org/), you can use `xacro4sdf` to write modularized SDF xml (not nest model) 8 | 9 | * xacro4sdf is similar, but different from [ros/xacro](https://github.com/ros/xacro) which is desiged for urdf. 10 | * xacro4sdf is simpler, but easier to use. 11 | 12 | > Reference: [ros/xacro](https://github.com/ros/xacro) 13 | > 14 | > * With Xacro, you can construct shorter and more readable XML files by using macros that expand to larger XML expressions. 15 | 16 | **Attention:** 17 | 18 | * `` is departed after version2.0.0, you can use the tag with version1.2.6. 19 | 20 | ## 1. Usage 21 | 22 | **Installation** 23 | 24 | ```bash 25 | #install by pip 26 | pip install xacro4sdf 27 | # or install from source code 28 | # git clone https://github.com/gezp/xacro4sdf.git 29 | # cd xacro4sdf && sudo python3 setup.py install 30 | ``` 31 | 32 | **create model.sdf.xmacro file** 33 | 34 | * refer to files in `test/` (for example `test/model.sdf.xmacro`) 35 | 36 | **generate model.sdf** 37 | 38 | ```bash 39 | #cd test 40 | xacro4sdf model.sdf.xmacro 41 | ``` 42 | 43 | * it will generate model.sdf (the result should be same as test/model.sdf) 44 | * more examples can be found `test` folder. 45 | 46 | **summary of XML Tags** 47 | 48 | * definition of property and macro : core function 49 | * `` and `` 50 | * include 51 | * `` :include definition of property and macro from other xmacro file 52 | * use of property and macro: 53 | * `${xxx}` : use of property ,it's very useful to use math expressions. 54 | * `` : use of macro, it's very useful for modular modeling. 55 | 56 | > Tip: 57 | > 58 | > * the xacro defination (`` , `` and ``) must be child node of root node `` . 59 | 60 | ## 2. Features 61 | 62 | * Properties 63 | * Macros 64 | * Math expressions 65 | * Include 66 | 67 | ### 2.1. Properties 68 | 69 | Properties are named values that can be inserted anywhere into the XML document 70 | 71 | **xacro definition** 72 | 73 | ```xml 74 | 75 | 76 | 77 | 78 | ``` 79 | 80 | **generated xml** 81 | 82 | ```xml 83 | 84 | ``` 85 | 86 | ### 2.2. Macros 87 | 88 | The main feature of `xacro4sdf` is macros. 89 | 90 | Define macros with the macro tag ``, then specify the macro name and a list of parameters. The list of parameters should be whitespace separated. 91 | 92 | The usage of Macros is to define `` which will be replaced with `` block according to the param `name`. 93 | 94 | **xacro definition** 95 | 96 | ```xml 97 | 98 | 99 | 100 | ${m} 101 | 102 | ${m*(y*y+z*z)/12} 103 | 0 104 | 0 105 | ${m*(x*x+z*z)/12} 106 | 0 107 | ${m*(x*x+z*z)/12} 108 | 109 | 110 | 111 | 112 | 0 0 0.02 0 0 0 113 | 114 | 115 | ``` 116 | 117 | **generated xml** 118 | 119 | ```xml 120 | 121 | 0 0 0.02 0 0 0 122 | 0.2 123 | 124 | 0.0008333333333333335 125 | 0 126 | 0 127 | 0.002166666666666667 128 | 0 129 | 0.002166666666666667 130 | 131 | 132 | ``` 133 | 134 | * only support simple parameters (string and number),but block parameters isn't supported. 135 | * it's supported to use other `xacro_macro` in `xacro_define_macro` which is recursive definition. 136 | 137 | > it's not recommended to define macro recursively (only support <=5). 138 | 139 | ### 2.3. Math expressions 140 | 141 | * within dollared-braces `${xxxx}`, you can also write simple math expressions. 142 | * refer to examples of **Properties** and **Macros** 143 | * it's implemented by calling `eval()` in python, so it's unsafe for some cases. 144 | 145 | ### 2.4. Including other xmacro files 146 | 147 | **definition include** 148 | 149 | You can include other xmacro files using the `` tag ,include other xmacro files according to param `uri`. 150 | 151 | * it will only include the definition of properties with tag `` and macros with tag ``. 152 | 153 | ```xml 154 | 155 | 156 | ``` 157 | 158 | * The uri for `model` means to search file in a list of folders which are defined by environment variable `IGN_GAZEBO_RESOURCE_PATH` and `GAZEBO_MODEL_PATH` 159 | * The uri for `file` means to open the file directly. 160 | * it try to open the file with relative path `simple_car/model.sdf.xmacro` . 161 | * you can also try to open file with absolute path `/simple_car/model.sdf.xmacro` with uri `file:///simple_car/model.sdf.xmacro`. 162 | 163 | > Tips: 164 | > 165 | > * `` supports to include recursively. 166 | > * Don't use same name for xacro definition (the param `name` of `` and ``) , otherwise the priority of xacro definition need be considered. 167 | > * Be carefully when using `` and `` 168 | 169 | ### 2.5 pre-defined common.xmacro 170 | 171 | ```xml 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | ``` 184 | 185 | * you can directly use these macros in your xmacro file. 186 | 187 | ## 3. Custom usage in python 188 | 189 | you can use xacro4sdf in python easily 190 | 191 | ```python 192 | from xacro4sdf.xacro4sdf import XMLMacro 193 | 194 | xmacro=XMLMacro() 195 | #case1 parse from file 196 | xmacro.set_xml_file(inputfile) 197 | xmacro.generate() 198 | xmacro.to_file(outputfile) 199 | 200 | #case2 parse from xml string 201 | xmacro.set_xml_string(xml_str) 202 | xmacro.generate() 203 | xmacro.to_file(outputfile) 204 | 205 | #case3 generate to string 206 | xmacro.set_xml_file(inputfile) 207 | xmacro.generate() 208 | xmacro.to_string() 209 | 210 | #case4 custom property 211 | xmacro.set_xml_file(inputfile) 212 | # use custom property dictionary to overwrite global property 213 | # defined by (only in 'inputfile') 214 | kv={"rplidar_a2_h":0.8} 215 | xmacro.generate(custom_property=kv) 216 | xmacro.to_file(outputfile) 217 | 218 | #case5 set model static 219 | xmacro.set_xml_file(inputfile) 220 | xmacro.generate() 221 | xmacro.set_static(True) 222 | xmacro.to_file(outputfile) 223 | 224 | ``` 225 | 226 | ## 4. Maintainer and License 227 | 228 | maintainer : Zhenpeng Ge, zhenpeng.ge@qq.com 229 | 230 | `xacro4sdf` is provided under MIT License. 231 | -------------------------------------------------------------------------------- /xacro4sdf/xacro4sdf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import sys 5 | import re 6 | import xml.dom.minidom 7 | import xacro4sdf.xml_format 8 | 9 | 10 | def try2number(str): 11 | try: 12 | return float(str) 13 | except ValueError: 14 | return str 15 | 16 | class XMLMacro: 17 | def __init__(self): 18 | # variables for xml parse 19 | self.local_property_dict={} 20 | self.global_property_dict={} 21 | self.macro_params_dict={} 22 | self.macro_content_dict={} 23 | self.xmacro_paths=[] 24 | # variables for xml info 25 | self.filename="" 26 | self.dirname="" 27 | self.in_doc=None 28 | self.out_doc=None 29 | self.parse_flag=False 30 | #init 31 | if os.getenv("IGN_GAZEBO_RESOURCE_PATH") is not None: 32 | self.xmacro_paths=self.xmacro_paths+os.getenv("IGN_GAZEBO_RESOURCE_PATH").split(":") 33 | if os.getenv("GAZEBO_MODEL_PATH") is not None: 34 | self.xmacro_paths=self.xmacro_paths+os.getenv("GAZEBO_MODEL_PATH").split(":") 35 | if os.getenv("XACRO4SDF_MODEL_PATH") is not None: 36 | self.xmacro_paths=self.xmacro_paths+os.getenv("XACRO4SDF_MODEL_PATH").split(":") 37 | 38 | ####private funtion 39 | # returh a absolute path. 40 | def __parse_uri(self,uri,current_dirname): 41 | #current_dirname is needed for 'file://' 42 | result = "" 43 | splited_str=uri.split("://") 44 | if len(splited_str)!=2: 45 | return result 46 | #get absolute path according to uri 47 | if splited_str[0] == "file": 48 | #to absolute path 49 | if(not os.path.isabs(splited_str[1])): 50 | splited_str[1]=os.path.join(current_dirname,splited_str[1]) 51 | if os.path.isfile(splited_str[1]): 52 | result = os.path.abspath(splited_str[1]) 53 | elif splited_str[0] == "model": 54 | for tmp_path in self.xmacro_paths: 55 | tmp_path = os.path.join(tmp_path,splited_str[1]) 56 | if(os.path.isfile(tmp_path)): 57 | result = tmp_path 58 | break 59 | return result 60 | 61 | def __get_xacro(self,doc): 62 | # only find in ... 63 | root = doc.documentElement 64 | for node in root.childNodes: 65 | if node.nodeType == xml.dom.Node.ELEMENT_NODE: 66 | if node.tagName == 'xacro_define_property': 67 | name = node.getAttribute("name") 68 | self.global_property_dict[name] = try2number(node.getAttribute("value")) 69 | elif node.tagName == 'xacro_define_macro': 70 | name = node.getAttribute("name") 71 | self.macro_params_dict[name] = node.getAttribute("params").split(' ') 72 | self.macro_content_dict[name] = node.toxml() 73 | 74 | def __get_include_xacro_recursively(self,doc,dirname): 75 | root = doc.documentElement 76 | for node in root.childNodes: 77 | if node.nodeType == xml.dom.Node.ELEMENT_NODE: 78 | if node.tagName == 'xacro_include_definition': 79 | uri = node.getAttribute("uri") 80 | filepath = self.__parse_uri(uri,dirname) 81 | if filepath != "": 82 | tmp_doc = xml.dom.minidom.parse(filepath) 83 | #get xacro from child recursively 84 | self.__get_include_xacro_recursively(tmp_doc,os.path.dirname(filepath)) 85 | #get xacro from file 86 | self.__get_xacro(tmp_doc) 87 | else: 88 | print("Error: not find xacro_include_definition uri",uri) 89 | sys.exit(1) 90 | 91 | def __remove_definition_xacro_node(self,doc): 92 | root = doc.documentElement 93 | for node in root.childNodes: 94 | if node.nodeType == xml.dom.Node.ELEMENT_NODE: 95 | if node.tagName == 'xacro_define_property' \ 96 | or node.tagName == 'xacro_define_macro' \ 97 | or node.tagName == 'xacro_include_definition': 98 | root.removeChild(node) 99 | 100 | def __re_eval_fn(self,obj): 101 | result = eval(obj.group(1), self.global_property_dict, self.local_property_dict) 102 | return str(result) 103 | 104 | def __eval_text(self,xml_str): 105 | pattern = re.compile(r'[$][{](.*?)[}]', re.S) 106 | return re.sub(pattern, self.__re_eval_fn, xml_str) 107 | 108 | def __replace_macro_node(self,node): 109 | parent = node.parentNode 110 | if not node.hasAttribute("name"): 111 | print("Error: check block,not find parameter name!") 112 | sys.exit(1) 113 | name = node.getAttribute("name") 114 | # get xml string 115 | xml_str = self.macro_content_dict[name] 116 | # get local table 117 | self.local_property_dict.clear() 118 | for param in self.macro_params_dict[name]: 119 | self.local_property_dict[param] = try2number(node.getAttribute(param)) 120 | # replace macro(insert and remove) 121 | xml_str = self.__eval_text(xml_str) 122 | new_node = xml.dom.minidom.parseString(xml_str).documentElement 123 | for cc in list(new_node.childNodes): 124 | parent.insertBefore(cc, node) 125 | parent.removeChild(node) 126 | 127 | 128 | 129 | ####public funtion 130 | def set_xml_file(self,filepath): 131 | self.filename=filepath 132 | self.dirname=os.path.dirname(os.path.abspath(filepath)) 133 | self.in_doc = xml.dom.minidom.parse(filepath) 134 | self.parse_flag=False 135 | 136 | def set_xml_string(self,xml_str): 137 | self.dirname=os.path.dirname(os.path.abspath(__file__)) 138 | self.in_doc = xml.dom.minidom.parse(xml_str) 139 | self.parse_flag=False 140 | 141 | def parse(self): 142 | # get xacro defination from in_doc(store macro's defination to dictionary) 143 | # get common xacro (lowest priority,it can be overwrited) 144 | common_xacro_path = os.path.join(os.path.dirname(os.path.abspath(__file__)) ,'common.xmacro') 145 | self.__get_xacro(xml.dom.minidom.parse(common_xacro_path)) 146 | # get inlcude xacro recursively (the priority depends on the order of tag) 147 | self.__get_include_xacro_recursively(self.in_doc,self.dirname) 148 | # get current xacro (highest priority) 149 | self.__get_xacro(self.in_doc) 150 | # remove xacro defination (,,) 151 | self.__remove_definition_xacro_node(self.in_doc) 152 | self.parse_flag=True 153 | 154 | def generate(self,custom_property:dict=None): 155 | if self.in_doc is None: 156 | self.out_doc=None 157 | return 158 | if not self.parse_flag: 159 | self.parse() 160 | # generate out_doc by relapcing xacro macro 161 | # replace global xacro property (process by string regular expression operations) 162 | self.local_property_dict.clear() 163 | if custom_property is not None: 164 | self.local_property_dict=custom_property.copy() 165 | xml_str = self.in_doc.documentElement.toxml() 166 | xml_str = self.__eval_text(xml_str) 167 | self.out_doc = xml.dom.minidom.parseString(xml_str) 168 | # replace xacro macro 169 | for _ in range(5): 170 | nodes = self.out_doc.getElementsByTagName("xacro_macro") 171 | if nodes.length != 0: 172 | for node in list(nodes): 173 | self.__replace_macro_node(node) 174 | else: 175 | break 176 | #check 177 | if self.out_doc.getElementsByTagName("xacro_macro").length != 0: 178 | print("Error:The recursive depth of macro defination only support <=5") 179 | self.out_doc=None 180 | return 181 | 182 | def set_static(self,is_static): 183 | if self.out_doc is None: 184 | return False 185 | root=self.out_doc.documentElement 186 | # model node 187 | nodes=root.getElementsByTagName('model') 188 | if len(nodes)==0: 189 | return False 190 | model=nodes[0] 191 | nodes=model.getElementsByTagName('static') 192 | # static node 193 | static = None 194 | if len(nodes)==0: 195 | static=self.out_doc.createElement('static') 196 | model.insertBefore(static,model.firstChild) 197 | static.appendChild(self.out_doc.createTextNode('false')) 198 | else: 199 | static=nodes[0] 200 | # set value 201 | static.firstChild.data = "true" if is_static else "false" 202 | return True 203 | 204 | def to_string(self): 205 | if self.out_doc is None: 206 | return None 207 | return self.out_doc.documentElement.toxml() 208 | 209 | def to_file(self,filepath,banner_info=None): 210 | # auto-generated banner 211 | # reference: https://github.com/ros/xacro/blob/noetic-devel/src/xacro/__init__.py 212 | if self.out_doc is None: 213 | return None 214 | src = "python script" 215 | if self.filename != "": 216 | src = self.filename 217 | if banner_info is not None: 218 | src=banner_info 219 | banner = [xml.dom.minidom.Comment(c) for c in 220 | [" %s " % ('=' * 83), 221 | " | This document was autogenerated by xacro4sdf from %-26s | " % src, 222 | " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " % "", 223 | " %s " % ('=' * 83)]] 224 | first = self.out_doc.firstChild 225 | for comment in banner: 226 | self.out_doc.insertBefore(comment, first) 227 | #write to file 228 | try: 229 | with open(filepath, 'w', encoding='UTF-8') as f: 230 | self.out_doc.writexml(f, indent='', addindent='\t', newl='\n', encoding='UTF-8') 231 | except Exception as err: 232 | print('to_file error:{0}'.format(err)) 233 | 234 | def xacro4sdf_main(): 235 | if(len(sys.argv) < 2): 236 | print("Usage: xacro4sdf (the name of inputfile must be xxx.xmacro)") 237 | return -1 238 | inputfile = sys.argv[1] 239 | outputfile = os.path.splitext(inputfile)[0] 240 | #check 241 | if os.path.splitext(inputfile)[1] != '.xmacro' and os.path.splitext(inputfile)[1] != '.xacro': 242 | print("Error: the name of inputfile must be xxx.xmacro") 243 | return -2 244 | #warn 245 | if os.path.splitext(inputfile)[1] == '.xacro': 246 | print("Warning: inputfile with xxx.xmacro is recommanded to show the difference from ros/xacro") 247 | #process 248 | xmacro=XMLMacro() 249 | xmacro.set_xml_file(inputfile) 250 | xmacro.generate() 251 | xmacro.to_file(outputfile) 252 | return 0 253 | 254 | 255 | if __name__ == '__main__': 256 | xacro4sdf_main() --------------------------------------------------------------------------------