Add more unit tests.
[pyyaml/python3.git] / lib / yaml / constructor.py
blobe85db36ef56ffadfd3abe97d3b013018145e1ebf
2 __all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
3 'ConstructorError']
5 from error import *
6 from nodes import *
7 from composer import *
9 try:
10 import datetime
11 datetime_available = True
12 except ImportError:
13 datetime_available = False
15 try:
16 set
17 except NameError:
18 from sets import Set as set
20 import binascii, re, sys
22 class ConstructorError(MarkedYAMLError):
23 pass
25 class BaseConstructor(Composer):
27 yaml_constructors = {}
28 yaml_multi_constructors = {}
30 def __init__(self):
31 self.constructed_objects = {}
32 self.recursive_objects = {}
34 def check_data(self):
35 # If there are more documents available?
36 return self.check_node()
38 def get_data(self):
39 # Construct and return the next document.
40 if self.check_node():
41 return self.construct_document(self.get_node())
43 def __iter__(self):
44 # Iterator protocol.
45 while self.check_node():
46 yield self.construct_document(self.get_node())
48 def construct_document(self, node):
49 data = self.construct_object(node)
50 self.constructed_objects = {}
51 self.recursive_objects = {}
52 return data
54 def construct_object(self, node):
55 if node in self.constructed_objects:
56 return self.constructed_objects[node]
57 if node in self.recursive_objects:
58 raise ConstructorError(None, None,
59 "found recursive node", node.start_mark)
60 self.recursive_objects[node] = None
61 constructor = None
62 if node.tag in self.yaml_constructors:
63 constructor = lambda node: self.yaml_constructors[node.tag](self, node)
64 else:
65 for tag_prefix in self.yaml_multi_constructors:
66 if node.tag.startswith(tag_prefix):
67 tag_suffix = node.tag[len(tag_prefix):]
68 constructor = lambda node: \
69 self.yaml_multi_constructors[tag_prefix](self, tag_suffix, node)
70 break
71 else:
72 if None in self.yaml_multi_constructors:
73 constructor = lambda node: \
74 self.yaml_multi_constructors[None](self, node.tag, node)
75 elif None in self.yaml_constructors:
76 constructor = lambda node: \
77 self.yaml_constructors[None](self, node)
78 elif isinstance(node, ScalarNode):
79 constructor = self.construct_scalar
80 elif isinstance(node, SequenceNode):
81 constructor = self.construct_sequence
82 elif isinstance(node, MappingNode):
83 constructor = self.construct_mapping
84 else:
85 print node.tag
86 data = constructor(node)
87 self.constructed_objects[node] = data
88 del self.recursive_objects[node]
89 return data
91 def construct_scalar(self, node):
92 if not isinstance(node, ScalarNode):
93 if isinstance(node, MappingNode):
94 for key_node in node.value:
95 if key_node.tag == u'tag:yaml.org,2002:value':
96 return self.construct_scalar(node.value[key_node])
97 raise ConstructorError(None, None,
98 "expected a scalar node, but found %s" % node.id,
99 node.start_mark)
100 return node.value
102 def construct_sequence(self, node):
103 if not isinstance(node, SequenceNode):
104 raise ConstructorError(None, None,
105 "expected a sequence node, but found %s" % node.id,
106 node.start_mark)
107 return [self.construct_object(child) for child in node.value]
109 def construct_mapping(self, node):
110 if not isinstance(node, MappingNode):
111 raise ConstructorError(None, None,
112 "expected a mapping node, but found %s" % node.id,
113 node.start_mark)
114 mapping = {}
115 merge = None
116 for key_node in node.value:
117 if key_node.tag == u'tag:yaml.org,2002:merge':
118 if merge is not None:
119 raise ConstructorError("while constructing a mapping", node.start_mark,
120 "found duplicate merge key", key_node.start_mark)
121 value_node = node.value[key_node]
122 if isinstance(value_node, MappingNode):
123 merge = [self.construct_mapping(value_node)]
124 elif isinstance(value_node, SequenceNode):
125 merge = []
126 for subnode in value_node.value:
127 if not isinstance(subnode, MappingNode):
128 raise ConstructorError("while constructing a mapping",
129 node.start_mark,
130 "expected a mapping for merging, but found %s"
131 % subnode.id, subnode.start_mark)
132 merge.append(self.construct_mapping(subnode))
133 merge.reverse()
134 else:
135 raise ConstructorError("while constructing a mapping", node.start_mark,
136 "expected a mapping or list of mappings for merging, but found %s"
137 % value_node.id, value_node.start_mark)
138 elif key_node.tag == u'tag:yaml.org,2002:value':
139 if '=' in mapping:
140 raise ConstructorError("while construction a mapping", node.start_mark,
141 "found duplicate value key", key_node.start_mark)
142 value = self.construct_object(node.value[key_node])
143 mapping['='] = value
144 else:
145 key = self.construct_object(key_node)
146 try:
147 duplicate_key = key in mapping
148 except TypeError, exc:
149 raise ConstructorError("while constructing a mapping", node.start_mark,
150 "found unacceptable key (%s)" % exc, key_node.start_mark)
151 if duplicate_key:
152 raise ConstructorError("while constructing a mapping", node.start_mark,
153 "found duplicate key", key_node.start_mark)
154 value = self.construct_object(node.value[key_node])
155 mapping[key] = value
156 if merge is not None:
157 merge.append(mapping)
158 mapping = {}
159 for submapping in merge:
160 mapping.update(submapping)
161 return mapping
163 def construct_pairs(self, node):
164 if not isinstance(node, MappingNode):
165 raise ConstructorError(None, None,
166 "expected a mapping node, but found %s" % node.id,
167 node.start_mark)
168 pairs = []
169 for key_node in node.value:
170 key = self.construct_object(key_node)
171 value = self.construct_object(node.value[key_node])
172 pairs.append((key, value))
173 return pairs
175 def add_constructor(cls, tag, constructor):
176 if not 'yaml_constructors' in cls.__dict__:
177 cls.yaml_constructors = cls.yaml_constructors.copy()
178 cls.yaml_constructors[tag] = constructor
179 add_constructor = classmethod(add_constructor)
181 def add_multi_constructor(cls, tag_prefix, multi_constructor):
182 if not 'yaml_multi_constructors' in cls.__dict__:
183 cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
184 cls.yaml_multi_constructors[tag_prefix] = multi_constructor
185 add_multi_constructor = classmethod(add_multi_constructor)
187 class SafeConstructor(BaseConstructor):
189 def construct_yaml_null(self, node):
190 self.construct_scalar(node)
191 return None
193 bool_values = {
194 u'yes': True,
195 u'no': False,
196 u'true': True,
197 u'false': False,
198 u'on': True,
199 u'off': False,
202 def construct_yaml_bool(self, node):
203 value = self.construct_scalar(node)
204 return self.bool_values[value.lower()]
206 def construct_yaml_int(self, node):
207 value = str(self.construct_scalar(node))
208 value = value.replace('_', '')
209 sign = +1
210 if value[0] == '-':
211 sign = -1
212 if value[0] in '+-':
213 value = value[1:]
214 if value == '0':
215 return 0
216 elif value.startswith('0b'):
217 return sign*int(value[2:], 2)
218 elif value.startswith('0x'):
219 return sign*int(value[2:], 16)
220 elif value[0] == '0':
221 return sign*int(value, 8)
222 elif ':' in value:
223 digits = [int(part) for part in value.split(':')]
224 digits.reverse()
225 base = 1
226 value = 0
227 for digit in digits:
228 value += digit*base
229 base *= 60
230 return sign*value
231 else:
232 return sign*int(value)
234 inf_value = 1e300000
235 nan_value = inf_value/inf_value
237 def construct_yaml_float(self, node):
238 value = str(self.construct_scalar(node))
239 value = value.replace('_', '')
240 sign = +1
241 if value[0] == '-':
242 sign = -1
243 if value[0] in '+-':
244 value = value[1:]
245 if value.lower() == '.inf':
246 return sign*self.inf_value
247 elif value.lower() == '.nan':
248 return self.nan_value
249 elif ':' in value:
250 digits = [float(part) for part in value.split(':')]
251 digits.reverse()
252 base = 1
253 value = 0.0
254 for digit in digits:
255 value += digit*base
256 base *= 60
257 return sign*value
258 else:
259 return float(value)
261 def construct_yaml_binary(self, node):
262 value = self.construct_scalar(node)
263 try:
264 return str(value).decode('base64')
265 except (binascii.Error, UnicodeEncodeError), exc:
266 raise ConstructorError(None, None,
267 "failed to decode base64 data: %s" % exc, node.start_mark)
269 timestamp_regexp = re.compile(
270 ur'''^(?P<year>[0-9][0-9][0-9][0-9])
271 -(?P<month>[0-9][0-9]?)
272 -(?P<day>[0-9][0-9]?)
273 (?:(?:[Tt]|[ \t]+)
274 (?P<hour>[0-9][0-9]?)
275 :(?P<minute>[0-9][0-9])
276 :(?P<second>[0-9][0-9])
277 (?:\.(?P<fraction>[0-9]*))?
278 (?:[ \t]*(?:Z|(?P<tz_hour>[-+][0-9][0-9]?)
279 (?::(?P<tz_minute>[0-9][0-9])?)?))?)?$''', re.X)
281 def construct_yaml_timestamp(self, node):
282 value = self.construct_scalar(node)
283 match = self.timestamp_regexp.match(node.value)
284 values = match.groupdict()
285 for key in values:
286 if values[key]:
287 values[key] = int(values[key])
288 else:
289 values[key] = 0
290 fraction = values['fraction']
291 if fraction:
292 while 10*fraction < 1000000:
293 fraction *= 10
294 values['fraction'] = fraction
295 stamp = datetime.datetime(values['year'], values['month'], values['day'],
296 values['hour'], values['minute'], values['second'], values['fraction'])
297 diff = datetime.timedelta(hours=values['tz_hour'], minutes=values['tz_minute'])
298 return stamp-diff
300 def construct_yaml_omap(self, node):
301 # Note: we do not check for duplicate keys, because it's too
302 # CPU-expensive.
303 if not isinstance(node, SequenceNode):
304 raise ConstructorError("while constructing an ordered map", node.start_mark,
305 "expected a sequence, but found %s" % node.id, node.start_mark)
306 omap = []
307 for subnode in node.value:
308 if not isinstance(subnode, MappingNode):
309 raise ConstructorError("while constructing an ordered map", node.start_mark,
310 "expected a mapping of length 1, but found %s" % subnode.id,
311 subnode.start_mark)
312 if len(subnode.value) != 1:
313 raise ConstructorError("while constructing an ordered map", node.start_mark,
314 "expected a single mapping item, but found %d items" % len(subnode.value),
315 subnode.start_mark)
316 key_node = subnode.value.keys()[0]
317 key = self.construct_object(key_node)
318 value = self.construct_object(subnode.value[key_node])
319 omap.append((key, value))
320 return omap
322 def construct_yaml_pairs(self, node):
323 # Note: the same code as `construct_yaml_omap`.
324 if not isinstance(node, SequenceNode):
325 raise ConstructorError("while constructing pairs", node.start_mark,
326 "expected a sequence, but found %s" % node.id, node.start_mark)
327 pairs = []
328 for subnode in node.value:
329 if not isinstance(subnode, MappingNode):
330 raise ConstructorError("while constructing pairs", node.start_mark,
331 "expected a mapping of length 1, but found %s" % subnode.id,
332 subnode.start_mark)
333 if len(subnode.value) != 1:
334 raise ConstructorError("while constructing pairs", node.start_mark,
335 "expected a single mapping item, but found %d items" % len(subnode.value),
336 subnode.start_mark)
337 key_node = subnode.value.keys()[0]
338 key = self.construct_object(key_node)
339 value = self.construct_object(subnode.value[key_node])
340 pairs.append((key, value))
341 return pairs
343 def construct_yaml_set(self, node):
344 value = self.construct_mapping(node)
345 return set(value)
347 def construct_yaml_str(self, node):
348 value = self.construct_scalar(node)
349 try:
350 return str(value)
351 except UnicodeEncodeError:
352 return value
354 def construct_yaml_seq(self, node):
355 return self.construct_sequence(node)
357 def construct_yaml_map(self, node):
358 return self.construct_mapping(node)
360 def construct_yaml_object(self, node, cls):
361 state = self.construct_mapping(node)
362 data = cls.__new__(cls)
363 if hasattr(data, '__setstate__'):
364 data.__setstate__(state)
365 else:
366 data.__dict__.update(state)
367 return data
369 def construct_undefined(self, node):
370 raise ConstructorError(None, None,
371 "could not determine a constructor for the tag %r" % node.tag.encode('utf-8'),
372 node.start_mark)
374 SafeConstructor.add_constructor(
375 u'tag:yaml.org,2002:null',
376 SafeConstructor.construct_yaml_null)
378 SafeConstructor.add_constructor(
379 u'tag:yaml.org,2002:bool',
380 SafeConstructor.construct_yaml_bool)
382 SafeConstructor.add_constructor(
383 u'tag:yaml.org,2002:int',
384 SafeConstructor.construct_yaml_int)
386 SafeConstructor.add_constructor(
387 u'tag:yaml.org,2002:float',
388 SafeConstructor.construct_yaml_float)
390 SafeConstructor.add_constructor(
391 u'tag:yaml.org,2002:binary',
392 SafeConstructor.construct_yaml_binary)
394 if datetime_available:
395 SafeConstructor.add_constructor(
396 u'tag:yaml.org,2002:timestamp',
397 SafeConstructor.construct_yaml_timestamp)
399 SafeConstructor.add_constructor(
400 u'tag:yaml.org,2002:omap',
401 SafeConstructor.construct_yaml_omap)
403 SafeConstructor.add_constructor(
404 u'tag:yaml.org,2002:pairs',
405 SafeConstructor.construct_yaml_pairs)
407 SafeConstructor.add_constructor(
408 u'tag:yaml.org,2002:set',
409 SafeConstructor.construct_yaml_set)
411 SafeConstructor.add_constructor(
412 u'tag:yaml.org,2002:str',
413 SafeConstructor.construct_yaml_str)
415 SafeConstructor.add_constructor(
416 u'tag:yaml.org,2002:seq',
417 SafeConstructor.construct_yaml_seq)
419 SafeConstructor.add_constructor(
420 u'tag:yaml.org,2002:map',
421 SafeConstructor.construct_yaml_map)
423 SafeConstructor.add_constructor(None,
424 SafeConstructor.construct_undefined)
426 class Constructor(SafeConstructor):
428 def construct_python_str(self, node):
429 return self.construct_scalar(node).encode('utf-8')
431 def construct_python_unicode(self, node):
432 return self.construct_scalar(node)
434 def construct_python_long(self, node):
435 return long(self.construct_yaml_int(node))
437 def construct_python_complex(self, node):
438 return complex(self.construct_scalar(node))
440 def construct_python_tuple(self, node):
441 return tuple(self.construct_yaml_seq(node))
443 def find_python_module(self, name, mark):
444 if not name:
445 raise ConstructorError("while constructing a Python module", mark,
446 "expected non-empty name appended to the tag", mark)
447 try:
448 __import__(name)
449 except ImportError, exc:
450 raise ConstructorError("while constructing a Python module", mark,
451 "cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
452 return sys.modules[name]
454 def find_python_name(self, name, mark):
455 if not name:
456 raise ConstructorError("while constructing a Python object", mark,
457 "expected non-empty name appended to the tag", mark)
458 if u'.' in name:
459 module_name, object_name = name.rsplit('.', 1)
460 else:
461 module_name = '__builtin__'
462 object_name = name
463 try:
464 __import__(module_name)
465 except ImportError, exc:
466 raise ConstructorError("while constructing a Python object", mark,
467 "cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
468 module = sys.modules[module_name]
469 if not hasattr(module, object_name):
470 raise ConstructorError("while constructing a Python object", mark,
471 "cannot find %r in the module %r" % (object_name.encode('utf-8'),
472 module.__name__), mark)
473 return getattr(module, object_name)
475 def construct_python_name(self, suffix, node):
476 value = self.construct_scalar(node)
477 if value:
478 raise ConstructorError("while constructing a Python name", node.start_mark,
479 "expected the empty value, but found %r" % value.encode('utf-8'),
480 node.start_mark)
481 return self.find_python_name(suffix, node.start_mark)
483 def construct_python_module(self, suffix, node):
484 value = self.construct_scalar(node)
485 if value:
486 raise ConstructorError("while constructing a Python module", node.start_mark,
487 "expected the empty value, but found %r" % value.encode('utf-8'),
488 node.start_mark)
489 return self.find_python_module(suffix, node.start_mark)
491 Constructor.add_constructor(
492 u'tag:yaml.org,2002:python/none',
493 Constructor.construct_yaml_null)
495 Constructor.add_constructor(
496 u'tag:yaml.org,2002:python/bool',
497 Constructor.construct_yaml_bool)
499 Constructor.add_constructor(
500 u'tag:yaml.org,2002:python/str',
501 Constructor.construct_python_str)
503 Constructor.add_constructor(
504 u'tag:yaml.org,2002:python/unicode',
505 Constructor.construct_python_unicode)
507 Constructor.add_constructor(
508 u'tag:yaml.org,2002:python/int',
509 Constructor.construct_yaml_int)
511 Constructor.add_constructor(
512 u'tag:yaml.org,2002:python/long',
513 Constructor.construct_python_long)
515 Constructor.add_constructor(
516 u'tag:yaml.org,2002:python/float',
517 Constructor.construct_yaml_float)
519 Constructor.add_constructor(
520 u'tag:yaml.org,2002:python/complex',
521 Constructor.construct_python_complex)
523 Constructor.add_constructor(
524 u'tag:yaml.org,2002:python/list',
525 Constructor.construct_yaml_seq)
527 Constructor.add_constructor(
528 u'tag:yaml.org,2002:python/tuple',
529 Constructor.construct_python_tuple)
531 Constructor.add_constructor(
532 u'tag:yaml.org,2002:python/dict',
533 Constructor.construct_yaml_map)
535 Constructor.add_multi_constructor(
536 u'tag:yaml.org,2002:python/name:',
537 Constructor.construct_python_name)
539 Constructor.add_multi_constructor(
540 u'tag:yaml.org,2002:python/module:',
541 Constructor.construct_python_module)