source: trunk/lib/syck/loaders.py @ 36

Revision 36, 12.5 KB checked in by xi, 9 years ago (diff)

Implement new YAML<->Python string conversion (fix #42).

Line 
1"""
2syck.loaders is a high-level wrapper for the Syck YAML parser.
3Do not use it directly, use the module 'syck' instead.
4"""
5
6# Python 2.2 compatibility
7from __future__ import generators
8
9try:
10    import datetime
11except ImportError:
12    pass
13
14try:
15    import sets
16except ImportError:
17    class _sets:
18        def Set(self, items):
19            set = {}
20            for items in items:
21                set[items] = None
22            return set
23    sets = _sets()
24
25import _syck
26
27import sys, re, warnings
28
29__all__ = ['GenericLoader', 'Loader',
30    'parse', 'load', 'parse_documents', 'load_documents',
31    'NotUnicodeInputWarning']
32
33class NotUnicodeInputWarning(UserWarning):
34    pass
35
36class GenericLoader(_syck.Parser):
37    """
38    GenericLoader constructs primitive Python objects from YAML documents.
39    """
40
41    def load(self):
42        """
43        Loads a YAML document from the source and return a native Python
44        object. On EOF, returns None and set the eof attribute on.
45        """
46        node = self.parse()
47        if self.eof:
48            return
49        return self._convert(node, {})
50
51    def _convert(self, node, node_to_object):
52        if node in node_to_object:
53            return node_to_object[node]
54        value = None
55        if node.kind == 'scalar':
56            value = node.value
57        elif node.kind == 'seq':
58            value = []
59            for item_node in node.value:
60                value.append(self._convert(item_node, node_to_object))
61        elif node.kind == 'map':
62            value = {}
63            for key_node in node.value:
64                key_object = self._convert(key_node, node_to_object)
65                value_object = self._convert(node.value[key_node],
66                        node_to_object)
67                try:
68                    if key_object in value:
69                        value = None
70                        break
71                    value[key_object] = value_object
72                except TypeError:
73                    value = None
74                    break
75            if value is None:
76                value = []
77                for key_node in node.value:
78                    key_object = self._convert(key_node, node_to_object)
79                    value_object = self._convert(node.value[key_node],
80                            node_to_object)
81                value.append((key_object, value_object))
82        node.value = value
83        object = self.construct(node)
84        node_to_object[node] = object
85        return object
86
87    def construct(self, node):
88        """Constructs a Python object by the given node."""
89        return node.value
90
91class Merge:
92    """Represents the merge key '<<'."""
93    pass
94
95class Default:
96    """Represents the default key '='."""
97    pass
98
99class Loader(GenericLoader):
100    """
101    Loader constructs native Python objects from YAML documents.
102    """
103
104    inf_value = 1e300000
105    nan_value = inf_value/inf_value
106
107    timestamp_expr = re.compile(r'(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)'
108            r'(?:'
109                r'(?:[Tt]|[ \t]+)(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)'
110                r'(?:\.(?P<micro>\d+)?)?'
111                r'[ \t]*(?:Z|(?P<zhour>[+-]\d\d)(?::(?P<zminute>\d\d))?)?'
112            r')?')
113
114    merge_key = Merge()
115    default_key = Default()
116
117    non_ascii = []
118    for i in range(256):
119        ch = chr(i)
120        if ch.isalnum():
121            non_ascii.append(ch)
122        else:
123            non_ascii.append('_')
124    non_ascii = ''.join(non_ascii)
125
126    python_bools = {'True': True, 'False': False}
127
128    class python_class:
129        pass
130
131    def find_constructor(self, node):
132        """
133        Returns the contructor for generating a Python object for the given
134        node.
135
136        The node tags are mapped to constructors by the following rule:
137
138        Tag                             Constructor
139        ---                             -----------
140        tag:yaml.org,2002:type          construct_type
141        tag:python.yaml.org,2002:type   construct_python_type
142        x-private:type                  construct_private_type
143        tag:domain.tld,2002:type        construct_domain_tld_2002_type
144
145        See the method code for more details.
146        """
147        parts = []
148        if node.tag:
149            parts = node.tag.split(':')
150        if parts:
151            if parts[0] == 'tag':
152                parts.pop(0)
153                if parts:
154                    if parts[0] == 'yaml.org,2002':
155                        parts.pop(0)
156                    elif parts[0] == 'python.yaml.org,2002':
157                        parts[0] = 'python'
158            elif parts[0] == 'x-private':
159                parts[0] = 'private'
160        parts = [part.translate(self.non_ascii) for part in parts]
161        while parts:
162            method = 'construct_'+'_'.join(parts)
163            if hasattr(self, method):
164                return getattr(self, method)
165            parts.pop()
166
167    def construct(self, node):
168        """Constructs a Python object by the given node."""
169        if node.kind == 'map' and self.merge_key in node.value:
170            self.merge_maps(node)
171        constructor = self.find_constructor(node)
172        if constructor:
173            return constructor(node)
174        else:
175            return node.value
176
177    def construct_null(self, node):
178        return None
179
180    def construct_bool_yes(self, node):
181        return True
182
183    def construct_bool_no(self, node):
184        return False
185
186    def construct_str(self, node):
187        try:
188            value = unicode(node.value, 'utf-8')
189        except UnicodeDecodeError:
190            warnings.warn("scalar value is not utf-8", NotUnicodeInputWarning)
191            return node.value
192        try:
193            return value.encode('ascii')
194        except UnicodeEncodeError:
195            return value
196
197    def construct_numeric_base60(self, num_type, node):
198        digits = [num_type(part) for part in node.value.split(':')]
199        digits.reverse()
200        base = 1
201        value = num_type(0)
202        for digit in digits:
203            value += digit*base
204            base *= 60
205        return value
206
207    def construct_int(self, node):
208        return int(node.value)
209
210    def construct_int_hex(self, node):
211        return int(node.value, 16)
212
213    def construct_int_oct(self, node):
214        return int(node.value, 8)
215
216    def construct_int_base60(self, node):
217        return self.construct_numeric_base60(int, node)
218
219    def construct_float(self, node):
220        return float(node.value)
221    construct_float_fix = construct_float
222    construct_float_exp = construct_float
223
224    def construct_float_base60(self, node):
225        return self.construct_numeric_base60(float, node)
226
227    def construct_float_inf(self, node):
228        return self.inf_value
229
230    def construct_float_neginf(self, node):
231        return -self.inf_value
232
233    def construct_float_nan(self, node):
234        return self.nan_value
235
236    def construct_binary(self, node):
237        return node.value.decode('base64')
238
239    def construct_timestamp(self, node):
240        match = self.timestamp_expr.match(node.value)
241        values = match.groupdict()
242        for key in values:
243            if values[key]:
244                values[key] = int(values[key])
245            else:
246                values[key] = 0
247        micro = values['micro']
248        if micro:
249            while 10*micro < 1000000:
250                micro *= 10
251        stamp = datetime.datetime(values['year'], values['month'], values['day'],
252                values['hour'], values['minute'], values['second'], micro)
253        diff = datetime.timedelta(hours=values['zhour'], minutes=values['zminute'])
254        return stamp-diff
255    construct_timestamp_ymd = construct_timestamp
256    construct_timestamp_iso8601 = construct_timestamp
257    construct_timestamp_spaced = construct_timestamp
258
259    def construct_merge(self, node):
260        return self.merge_key
261
262    def construct_default(self, node):
263        return self.default_key
264
265    def merge_maps(self, node):
266        maps = node.value[self.merge_key]
267        del node.value[self.merge_key]
268        if not isinstance(maps, list):
269            maps = [maps]
270        maps.reverse()
271        maps.append(node.value.copy())
272        for item in maps:
273            node.value.update(item)
274
275    def construct_omap(self, node):
276        omap = []
277        for mapping in node.value:
278            for key in mapping:
279                omap.append((key, mapping[key]))
280        return omap
281
282    def construct_pairs(self, node): # Same as construct_omap.
283        pairs = []
284        for mapping in node.value:
285            for key in mapping:
286                pairs.append((key, mapping[key]))
287        return pairs
288
289    def construct_set(self, node):
290        return sets.Set(node.value)
291
292    def construct_python_none(self, node):
293        return None
294
295    def construct_python_bool(self, node):
296        return self.python_bools[node.value]
297
298    def construct_python_int(self, node):
299        return int(node.value)
300
301    def construct_python_long(self, node):
302        return long(node.value)
303
304    def construct_python_float(self, node):
305        return float(node.value)
306
307    def construct_python_str(self, node):
308        return str(node.value)
309
310    def construct_python_unicode(self, node):
311        return unicode(node.value, 'utf-8')
312
313    def construct_python_list(self, node):
314        return node.value
315
316    def construct_python_tuple(self, node):
317        return tuple(node.value)
318
319    def construct_python_dict(self, node):
320        return node.value
321
322    def find_python_object(self, node):
323        full_name = node.tag.split(':')[3]
324        parts = full_name.split('.')
325        object_name = parts.pop()
326        module_name = '.'.join(parts)
327        if not module_name:
328            module_name = '__builtin__'
329        else:
330            __import__(module_name)
331        return getattr(sys.modules[module_name], object_name)
332
333    def find_python_state(self, node):
334        if node.kind == 'seq':
335            args = node.value
336            kwds = {}
337            state = {}
338        else:
339            args = node.value.get('args', [])
340            kwds = node.value.get('kwds', {})
341            state = node.value.get('state', {})
342        return args, kwds, state
343
344    def set_python_state(self, object, state):
345        if hasattr(object, '__setstate__'):
346            object.__setstate__(state)
347        else:
348            slotstate = {}
349            if isinstance(state, tuple) and len(state) == 2:
350                state, slotstate = state
351            if hasattr(object, '__dict__'):
352                object.__dict__.update(state)
353            elif state:
354                slotstate.update(state)
355            for key, value in slotstate.items():
356                setattr(object, key, value)
357
358    def construct_python_name(self, node):
359        return self.find_python_object(node)
360
361    def construct_python_object(self, node):
362        cls = self.find_python_object(node)
363        if type(cls) is type(self.python_class):
364            if hasattr(cls, '__getnewargs__'):
365                object = cls()
366            else:
367                object = self.python_class()
368                object.__class__ = cls
369        else:
370            object = cls.__new__(cls)
371        self.set_python_state(object, node.value)
372        return object
373
374    def construct_python_new(self, node):
375        cls = self.find_python_object(node)
376        args, kwds, state = self.find_python_state(node)
377        if type(cls) is type(self.python_class):
378            object = cls(*args, **kwds)
379        else:
380            object = cls.__new__(cls, *args, **kwds)
381        self.set_python_state(object, state)
382        return object
383
384    def construct_python_apply(self, node):
385        constructor = self.find_python_object(node)
386        args, kwds, state = self.find_python_state(node)
387        object = constructor(*args, **kwds)
388        self.set_python_state(object, state)
389        return object
390
391def parse(source, Loader=Loader, **parameters):
392    """Parses 'source' and returns the root of the 'Node' graph."""
393    loader = Loader(source, **parameters)
394    return loader.parse()
395
396def load(source, Loader=Loader, **parameters):
397    """Parses 'source' and returns the root object."""
398    loader = Loader(source, **parameters)
399    return loader.load()
400
401def parse_documents(source, Loader=Loader, **parameters):
402    """Iterates over 'source' and yields the root 'Node' for each document."""
403    loader = Loader(source, **parameters)
404    while True:
405        node = loader.parse()
406        if loader.eof:
407            break
408        yield node
409
410def load_documents(source, Loader=Loader, **parameters):
411    """Iterates over 'source' and yields the root object for each document."""
412    loader = Loader(source, **parameters)
413    while True:
414        object = loader.load()
415        if loader.eof:
416            break
417        yield object
418
Note: See TracBrowser for help on using the repository browser.