Fix a few bugs.
[pyyaml/python3.git] / lib / yaml / constructor.py
blob1e7fea0ca95bebf4d18706f40ef6613c99049bb7
2 __all__ = ['BaseConstructor', 'Constructor', 'ConstructorError',
3 'YAMLObject', 'YAMLObjectMetaclass']
5 from error import *
6 from nodes import *
8 try:
9 import datetime
10 datetime_available = True
11 except ImportError:
12 datetime_available = False
14 try:
15 set
16 except NameError:
17 from sets import Set as set
19 import binascii, re
21 class ConstructorError(MarkedYAMLError):
22 pass
24 class BaseConstructor:
26 def __init__(self, resolver):
27 self.resolver = resolver
28 self.constructed_objects = {}
30 def check(self):
31 # If there are more documents available?
32 return self.resolver.check()
34 def get(self):
35 # Construct and return the next document.
36 if self.resolver.check():
37 return self.construct_document(self.resolver.get())
39 def __iter__(self):
40 # Iterator protocol.
41 while self.resolver.check():
42 yield self.construct_document(self.resolver.get())
44 def construct_document(self, node):
45 native = self.construct_object(node)
46 self.constructed_objects = {}
47 return native
49 def construct_object(self, node):
50 if node in self.constructed_objects:
51 return self.constructed_objects[node]
52 if node.tag in self.yaml_constructors:
53 native = self.yaml_constructors[node.tag](self, node)
54 elif None in self.yaml_constructors:
55 native = self.yaml_constructors[None](self, node)
56 elif isinstance(node, ScalarNode):
57 native = self.construct_scalar(node)
58 elif isinstance(node, SequenceNode):
59 native = self.construct_sequence(node)
60 elif isinstance(node, MappingNode):
61 native = self.construct_mapping(node)
62 self.constructed_objects[node] = native
63 return native
65 def construct_scalar(self, node):
66 if not isinstance(node, ScalarNode):
67 if isinstance(node, MappingNode):
68 for key_node in node.value:
69 if key_node.tag == u'tag:yaml.org,2002:value':
70 return self.construct_scalar(node.value[key_node])
71 raise ConstructorError(None, None,
72 "expected a scalar node, but found %s" % node.id,
73 node.start_marker)
74 return node.value
76 def construct_sequence(self, node):
77 if not isinstance(node, SequenceNode):
78 raise ConstructorError(None, None,
79 "expected a sequence node, but found %s" % node.id,
80 node.start_marker)
81 return [self.construct_object(child) for child in node.value]
83 def construct_mapping(self, node):
84 if not isinstance(node, MappingNode):
85 raise ConstructorError(None, None,
86 "expected a mapping node, but found %s" % node.id,
87 node.start_marker)
88 mapping = {}
89 merge = None
90 for key_node in node.value:
91 if key_node.tag == u'tag:yaml.org,2002:merge':
92 if merge is not None:
93 raise ConstructorError("while constructing a mapping", node.start_marker,
94 "found duplicate merge key", key_node.start_marker)
95 value_node = node.value[key_node]
96 if isinstance(value_node, MappingNode):
97 merge = [self.construct_mapping(value_node)]
98 elif isinstance(value_node, SequenceNode):
99 merge = []
100 for subnode in value_node.value:
101 if not isinstance(subnode, MappingNode):
102 raise ConstructorError("while constructing a mapping",
103 node.start_marker,
104 "expected a mapping for merging, but found %s"
105 % subnode.id, subnode.start_marker)
106 merge.append(self.construct_mapping(subnode))
107 merge.reverse()
108 else:
109 raise ConstructorError("while constructing a mapping", node.start_marker,
110 "expected a mapping or list of mappings for merging, but found %s"
111 % value_node.id, value_node.start_marker)
112 elif key_node.tag == u'tag:yaml.org,2002:value':
113 if '=' in mapping:
114 raise ConstructorError("while construction a mapping", node.start_marker,
115 "found duplicate value key", key_node.start_marker)
116 value = self.construct_object(node.value[key_node])
117 mapping['='] = value
118 else:
119 key = self.construct_object(key_node)
120 try:
121 duplicate_key = key in mapping
122 except TypeError, exc:
123 raise ConstructorError("while constructing a mapping", node.start_marker,
124 "found unacceptable key (%s)" % exc, key_node.start_marker)
125 if duplicate_key:
126 raise ConstructorError("while constructing a mapping", node.start_marker,
127 "found duplicate key", key_node.start_marker)
128 value = self.construct_object(node.value[key_node])
129 mapping[key] = value
130 if merge is not None:
131 merge.append(mapping)
132 mapping = {}
133 for submapping in merge:
134 mapping.update(submapping)
135 return mapping
137 def construct_pairs(self, node):
138 if not isinstance(node, MappingNode):
139 raise ConstructorError(None, None,
140 "expected a mapping node, but found %s" % node.id,
141 node.start_marker)
142 pairs = []
143 for key_node in node.value:
144 key = self.construct_object(key_node)
145 value = self.construct_object(node.value[key_node])
146 pairs.append((key, value))
147 return pairs
149 def add_constructor(cls, tag, constructor):
150 if not 'yaml_constructors' in cls.__dict__:
151 cls.yaml_constructors = cls.yaml_constructors.copy()
152 cls.yaml_constructors[tag] = constructor
153 add_constructor = classmethod(add_constructor)
155 yaml_constructors = {}
157 class Constructor(BaseConstructor):
159 def construct_yaml_null(self, node):
160 self.construct_scalar(node)
161 return None
163 bool_values = {
164 u'y': True,
165 u'yes': True,
166 u'n': False,
167 u'no': False,
168 u'true': True,
169 u'false': False,
170 u'on': True,
171 u'off': False,
174 def construct_yaml_bool(self, node):
175 value = self.construct_scalar(node)
176 return self.bool_values[value.lower()]
178 def construct_yaml_int(self, node):
179 value = str(self.construct_scalar(node))
180 value = value.replace('_', '')
181 sign = +1
182 if value[0] == '-':
183 sign = -1
184 if value[0] in '+-':
185 value = value[1:]
186 if value == '0':
187 return 0
188 elif value.startswith('0b'):
189 return sign*int(value[2:], 2)
190 elif value.startswith('0x'):
191 return sign*int(value[2:], 16)
192 elif value[0] == '0':
193 return sign*int(value, 8)
194 elif ':' in value:
195 digits = [int(part) for part in value.split(':')]
196 digits.reverse()
197 base = 1
198 value = 0
199 for digit in digits:
200 value += digit*base
201 base *= 60
202 return sign*value
203 else:
204 return sign*int(value)
206 inf_value = 1e300000
207 nan_value = inf_value/inf_value
209 def construct_yaml_float(self, node):
210 value = str(self.construct_scalar(node))
211 value = value.replace('_', '')
212 sign = +1
213 if value[0] == '-':
214 value = -1
215 if value[0] in '+-':
216 value = value[1:]
217 if value.lower() == '.inf':
218 return sign*self.inf_value
219 elif value.lower() == '.nan':
220 return self.nan_value
221 elif ':' in value:
222 digits = [float(part) for part in value.split(':')]
223 digits.reverse()
224 base = 1
225 value = 0.0
226 for digit in digits:
227 value += digit*base
228 base *= 60
229 return sign*value
230 else:
231 return float(value)
233 def construct_yaml_binary(self, node):
234 value = self.construct_scalar(node)
235 try:
236 return str(value).decode('base64')
237 except (binascii.Error, UnicodeEncodeError), exc:
238 raise ConstructorError(None, None,
239 "failed to decode base64 data: %s" % exc, node.start_mark)
241 timestamp_regexp = re.compile(
242 ur'''^(?P<year>[0-9][0-9][0-9][0-9])
243 -(?P<month>[0-9][0-9]?)
244 -(?P<day>[0-9][0-9]?)
245 (?:[Tt]|[ \t]+)
246 (?P<hour>[0-9][0-9]?)
247 :(?P<minute>[0-9][0-9])
248 :(?P<second>[0-9][0-9])
249 (?:\.(?P<fraction>[0-9]*))?
250 (?:[ \t]*(?:Z|(?P<tz_hour>[-+][0-9][0-9]?)
251 (?::(?P<tz_minute>[0-9][0-9])?)))?$''', re.X),
253 def construct_yaml_timestamp(self, node):
254 value = self.construct_scalar(node)
255 match = self.timestamp_expr.match(node.value)
256 values = match.groupdict()
257 for key in values:
258 if values[key]:
259 values[key] = int(values[key])
260 else:
261 values[key] = 0
262 fraction = values['fraction']
263 if micro:
264 while 10*fraction < 1000000:
265 fraction *= 10
266 values['fraction'] = fraction
267 stamp = datetime.datetime(values['year'], values['month'], values['day'],
268 values['hour'], values['minute'], values['second'], values['fraction'])
269 diff = datetime.timedelta(hours=values['tz_hour'], minutes=values['tz_minute'])
270 return stamp-diff
272 def construct_yaml_omap(self, node):
273 # Note: we do not check for duplicate keys, because it's too
274 # CPU-expensive.
275 if not isinstance(node, SequenceNode):
276 raise ConstructorError("while constructing an ordered map", node.start_marker,
277 "expected a sequence, but found %s" % node.id, node.start_marker)
278 omap = []
279 for subnode in node.value:
280 if not isinstance(subnode, MappingNode):
281 raise ConstructorError("while constructing an ordered map", node.start_marker,
282 "expected a mapping of length 1, but found %s" % subnode.id,
283 subnode.start_marker)
284 if len(subnode.value) != 1:
285 raise ConstructorError("while constructing an ordered map", node.start_marker,
286 "expected a single mapping item, but found %d items" % len(subnode.value),
287 subnode.start_marker)
288 key_node = subnode.value.keys()[0]
289 key = self.construct_object(key_node)
290 value = self.construct_object(subnode.value[key_node])
291 omap.append((key, value))
293 def construct_yaml_pairs(self, node):
294 # Note: the same code as `construct_yaml_omap`.
295 if not isinstance(node, SequenceNode):
296 raise ConstructorError("while constructing pairs", node.start_marker,
297 "expected a sequence, but found %s" % node.id, node.start_marker)
298 omap = []
299 for subnode in node.value:
300 if not isinstance(subnode, MappingNode):
301 raise ConstructorError("while constructing pairs", node.start_marker,
302 "expected a mapping of length 1, but found %s" % subnode.id,
303 subnode.start_marker)
304 if len(subnode.value) != 1:
305 raise ConstructorError("while constructing pairs", node.start_marker,
306 "expected a single mapping item, but found %d items" % len(subnode.value),
307 subnode.start_marker)
308 key_node = subnode.value.keys()[0]
309 key = self.construct_object(key_node)
310 value = self.construct_object(subnode.value[key_node])
311 omap.append((key, value))
313 def construct_yaml_set(self, node):
314 value = self.construct_mapping(node)
315 return set(value)
317 def construct_yaml_str(self, node):
318 value = self.construct_scalar(node)
319 try:
320 return str(value)
321 except UnicodeEncodeError:
322 return value
324 def construct_yaml_seq(self, node):
325 return self.construct_sequence(node)
327 def construct_yaml_map(self, node):
328 return self.construct_mapping(node)
330 def construct_undefined(self, node):
331 raise ConstructorError(None, None,
332 "could not determine a constructor for the tag %r" % node.tag.encode('utf-8'),
333 node.start_marker)
335 Constructor.add_constructor(
336 u'tag:yaml.org,2002:null',
337 Constructor.construct_yaml_null)
339 Constructor.add_constructor(
340 u'tag:yaml.org,2002:bool',
341 Constructor.construct_yaml_bool)
343 Constructor.add_constructor(
344 u'tag:yaml.org,2002:int',
345 Constructor.construct_yaml_int)
347 Constructor.add_constructor(
348 u'tag:yaml.org,2002:float',
349 Constructor.construct_yaml_float)
351 Constructor.add_constructor(
352 u'tag:yaml.org,2002:timestamp',
353 Constructor.construct_yaml_timestamp)
355 Constructor.add_constructor(
356 u'tag:yaml.org,2002:omap',
357 Constructor.construct_yaml_omap)
359 Constructor.add_constructor(
360 u'tag:yaml.org,2002:pairs',
361 Constructor.construct_yaml_pairs)
363 Constructor.add_constructor(
364 u'tag:yaml.org,2002:set',
365 Constructor.construct_yaml_set)
367 Constructor.add_constructor(
368 u'tag:yaml.org,2002:str',
369 Constructor.construct_yaml_str)
371 Constructor.add_constructor(
372 u'tag:yaml.org,2002:seq',
373 Constructor.construct_yaml_seq)
375 Constructor.add_constructor(
376 u'tag:yaml.org,2002:map',
377 Constructor.construct_yaml_map)
379 Constructor.add_constructor(None,
380 Constructor.construct_undefined)
382 class YAMLObjectMetaclass(type):
384 def __init__(cls, name, bases, kwds):
385 super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
386 if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
387 cls.yaml_constructor_class.add_constructor(cls.yaml_tag, cls.from_yaml)
389 class YAMLObject(object):
391 __metaclass__ = YAMLObjectMetaclass
393 yaml_constructor_class = Constructor
395 yaml_tag = None
397 def from_yaml(cls, constructor, node):
398 raise ConstructorError(None, None,
399 "found undefined constructor for the tag %r"
400 % node.tag.encode('utf-8'), node.start_marker)
401 from_yaml = classmethod(from_yaml)
403 def to_yaml(self):
404 assert False # needs dumper