26
26
27
27
28
28
def from_xml_string (xml_string , escape_separators = False ,
29
- model_dir = '' , resolve_references = True , assets = None ):
29
+ model_dir = '' , resolve_references = True , assets = None ,
30
+ base_model_dir = None ):
30
31
"""Parses an XML string into an MJCF object model.
31
32
32
33
Args:
@@ -41,6 +42,9 @@ def from_xml_string(xml_string, escape_separators=False,
41
42
assets: (optional) A dictionary of pre-loaded assets, of the form
42
43
`{filename: bytestring}`. If present, PyMJCF will search for assets in
43
44
this dictionary before attempting to load them from the filesystem.
45
+ base_model_dir: (optional) Path to the directory containing the base model.
46
+ This is used to prefix the paths of <include> elements' file attributes
47
+ to support nested includes as in the MuJoCo compiler.
44
48
45
49
Returns:
46
50
An `mjcf.RootElement`.
@@ -49,11 +53,12 @@ def from_xml_string(xml_string, escape_separators=False,
49
53
return _parse (xml_root , escape_separators ,
50
54
model_dir = model_dir ,
51
55
resolve_references = resolve_references ,
52
- assets = assets )
56
+ assets = assets , base_model_dir = base_model_dir )
53
57
54
58
55
59
def from_file (file_handle , escape_separators = False ,
56
- model_dir = '' , resolve_references = True , assets = None ):
60
+ model_dir = '' , resolve_references = True , assets = None ,
61
+ base_model_dir = None ):
57
62
"""Parses an XML file into an MJCF object model.
58
63
59
64
Args:
@@ -68,6 +73,9 @@ def from_file(file_handle, escape_separators=False,
68
73
assets: (optional) A dictionary of pre-loaded assets, of the form
69
74
`{filename: bytestring}`. If present, PyMJCF will search for assets in
70
75
this dictionary before attempting to load them from the filesystem.
76
+ base_model_dir: (optional) Path to the directory containing the base model.
77
+ This is used to prefix the paths of <include> elements' file attributes
78
+ to support nested includes as in the MuJoCo compiler.
71
79
72
80
Returns:
73
81
An `mjcf.RootElement`.
@@ -76,11 +84,11 @@ def from_file(file_handle, escape_separators=False,
76
84
return _parse (xml_root , escape_separators ,
77
85
model_dir = model_dir ,
78
86
resolve_references = resolve_references ,
79
- assets = assets )
87
+ assets = assets , base_model_dir = base_model_dir )
80
88
81
89
82
90
def from_path (path , escape_separators = False , resolve_references = True ,
83
- assets = None ):
91
+ assets = None , base_model_dir = None ):
84
92
"""Parses an XML file into an MJCF object model.
85
93
86
94
Args:
@@ -94,6 +102,9 @@ def from_path(path, escape_separators=False, resolve_references=True,
94
102
assets: (optional) A dictionary of pre-loaded assets, of the form
95
103
`{filename: bytestring}`. If present, PyMJCF will search for assets in
96
104
this dictionary before attempting to load them from the filesystem.
105
+ base_model_dir: (optional) Path to the directory containing the base model.
106
+ This is used to prefix the paths of <include> elements' file attributes
107
+ to support nested includes as in the MuJoCo compiler.
97
108
98
109
Returns:
99
110
An `mjcf.RootElement`.
@@ -103,11 +114,12 @@ def from_path(path, escape_separators=False, resolve_references=True,
103
114
xml_root = etree .fromstring (contents )
104
115
return _parse (xml_root , escape_separators ,
105
116
model_dir = model_dir , resolve_references = resolve_references ,
106
- assets = assets )
117
+ assets = assets , base_model_dir = base_model_dir )
107
118
108
119
109
120
def _parse (xml_root , escape_separators = False ,
110
- model_dir = '' , resolve_references = True , assets = None ):
121
+ model_dir = '' , resolve_references = True , assets = None ,
122
+ base_model_dir = None ):
111
123
"""Parses a complete MJCF model from an XML.
112
124
113
125
Args:
@@ -122,6 +134,9 @@ def _parse(xml_root, escape_separators=False,
122
134
assets: (optional) A dictionary of pre-loaded assets, of the form
123
135
`{filename: bytestring}`. If present, PyMJCF will search for assets in
124
136
this dictionary before attempting to load them from the filesystem.
137
+ base_model_dir: (optional) Path to the directory containing the base model.
138
+ This is used to prefix the paths of <include> elements' file attributes
139
+ to support nested includes as in the MuJoCo compiler.
125
140
126
141
Returns:
127
142
An `mjcf.RootElement`.
@@ -140,20 +155,9 @@ def _parse(xml_root, escape_separators=False,
140
155
# Recursively parse any included XML files.
141
156
to_include = []
142
157
for include_tag in xml_root .findall ('include' ):
143
- try :
144
- # First look for the path to the included XML file in the assets dict.
145
- path_or_xml_string = assets [include_tag .attrib ['file' ]]
146
- parsing_func = from_xml_string
147
- except KeyError :
148
- # If it's not present in the assets dict then attempt to load the XML
149
- # from the filesystem.
150
- path_or_xml_string = os .path .join (model_dir , include_tag .attrib ['file' ])
151
- parsing_func = from_path
152
- included_mjcf = parsing_func (
153
- path_or_xml_string ,
154
- escape_separators = escape_separators ,
155
- resolve_references = resolve_references ,
156
- assets = assets )
158
+ included_mjcf = _parse_include (include_tag , escape_separators , model_dir ,
159
+ resolve_references , assets , base_model_dir )
160
+
157
161
to_include .append (included_mjcf )
158
162
# We must remove <include/> tags before parsing the main XML file, since
159
163
# these are a schema violation.
@@ -165,7 +169,7 @@ def _parse(xml_root, escape_separators=False,
165
169
except KeyError :
166
170
model = None
167
171
mjcf_root = element .RootElement (
168
- model = model , model_dir = model_dir , assets = assets )
172
+ model = model , model_dir = base_model_dir or model_dir , assets = assets )
169
173
_parse_children (xml_root , mjcf_root , escape_separators )
170
174
171
175
# Merge in the included XML files.
@@ -180,6 +184,70 @@ def _parse(xml_root, escape_separators=False,
180
184
return mjcf_root
181
185
182
186
187
+ def _parse_include (include_tag , escape_separators , model_dir , resolve_references , assets , base_model_dir ):
188
+ """
189
+ Parses an included XML file.
190
+
191
+ Args:
192
+ include_tag: An `etree.Element` object with tag 'include'.
193
+ escape_separators: (optional) A boolean, whether to replace '/' characters
194
+ in element identifiers. If `False`, any '/' present in the XML causes
195
+ a ValueError to be raised.
196
+ model_dir: (optional) Path to the directory containing the model XML file.
197
+ This is used to prefix the paths of all asset files.
198
+ resolve_references: (optional) A boolean indicating whether the parser
199
+ should attempt to resolve reference attributes to a corresponding element.
200
+ assets: (optional) A dictionary of pre-loaded assets, of the form
201
+ `{filename: bytestring}`. If present, PyMJCF will search for assets in
202
+ this dictionary before attempting to load them from the filesystem.
203
+ base_model_dir: (optional) Path to the directory containing the base model.
204
+ This is used to prefix the paths of <include> elements' file attributes
205
+ to support nested includes as in the MuJoCo compiler.
206
+
207
+ Returns:
208
+ An `mjcf.RootElement`.
209
+
210
+ Raises:
211
+ FileNotFoundError: If the included the inner paths of the included XML could
212
+ not be resolved.
213
+ """
214
+
215
+ base_dirs = [model_dir ] # always look in the current model dir first
216
+ if base_model_dir is not None :
217
+ base_dirs .append (base_model_dir ) # then look in the base model dir if provided
218
+
219
+ not_found_exception = None # a container for the final exception if some file references are not resolved
220
+
221
+ # try to parse the included XML file from each of the base dirs
222
+ for working_dir in base_dirs :
223
+
224
+ # setup new parsing kwargs dict with current base model dir
225
+ parsing_func_kwargs = dict (
226
+ escape_separators = escape_separators ,
227
+ resolve_references = resolve_references ,
228
+ assets = assets ,
229
+ base_model_dir = working_dir
230
+ )
231
+
232
+ try :
233
+ # First look for the path to the included XML file in the assets dict.
234
+ path_or_xml_string = assets [include_tag .attrib ['file' ]]
235
+ parsing_func = from_xml_string
236
+ parsing_func_kwargs .update (dict (model_dir = working_dir )) # requires explicit model dir
237
+ except KeyError :
238
+ # If it's not present in the assets dict then attempt to load the XML
239
+ # from the filesystem.
240
+ path_or_xml_string = os .path .join (working_dir , include_tag .attrib ['file' ])
241
+ parsing_func = from_path
242
+ try :
243
+ # if successfully parsed the included XML file, stop searching
244
+ return parsing_func (path_or_xml_string , ** parsing_func_kwargs )
245
+ except FileNotFoundError as e :
246
+ # base model dir did not resolve the inner include paths
247
+ not_found_exception = e
248
+
249
+ raise FileNotFoundError ('Could not find an appropriate base path for include tag' ) from not_found_exception
250
+
183
251
def _parse_children (xml_element , mjcf_element , escape_separators = False ):
184
252
"""Parses all children of a given XML element into an MJCF element.
185
253
0 commit comments