source: pyyaml/trunk/lib3/yaml/emitter.py @ 328

Revision 328, 41.8 KB checked in by xi, 5 years ago (diff)

Added basic support for Python 3 (Thanks idadesub(at)users(dot)sourceforge(dot)net).

Line 
1
2# Emitter expects events obeying the following grammar:
3# stream ::= STREAM-START document* STREAM-END
4# document ::= DOCUMENT-START node DOCUMENT-END
5# node ::= SCALAR | sequence | mapping
6# sequence ::= SEQUENCE-START node* SEQUENCE-END
7# mapping ::= MAPPING-START (node node)* MAPPING-END
8
9__all__ = ['Emitter', 'EmitterError']
10
11from .error import YAMLError
12from .events import *
13
14class EmitterError(YAMLError):
15    pass
16
17class ScalarAnalysis:
18    def __init__(self, scalar, empty, multiline,
19            allow_flow_plain, allow_block_plain,
20            allow_single_quoted, allow_double_quoted,
21            allow_block):
22        self.scalar = scalar
23        self.empty = empty
24        self.multiline = multiline
25        self.allow_flow_plain = allow_flow_plain
26        self.allow_block_plain = allow_block_plain
27        self.allow_single_quoted = allow_single_quoted
28        self.allow_double_quoted = allow_double_quoted
29        self.allow_block = allow_block
30
31class Emitter:
32
33    DEFAULT_TAG_PREFIXES = {
34        '!' : '!',
35        'tag:yaml.org,2002:' : '!!',
36    }
37
38    def __init__(self, stream, canonical=None, indent=None, width=None,
39            allow_unicode=None, line_break=None):
40
41        # The stream should have the methods `write` and possibly `flush`.
42        self.stream = stream
43
44        # Encoding can be overriden by STREAM-START.
45        self.encoding = None
46
47        # Emitter is a state machine with a stack of states to handle nested
48        # structures.
49        self.states = []
50        self.state = self.expect_stream_start
51
52        # Current event and the event queue.
53        self.events = []
54        self.event = None
55
56        # The current indentation level and the stack of previous indents.
57        self.indents = []
58        self.indent = None
59
60        # Flow level.
61        self.flow_level = 0
62
63        # Contexts.
64        self.root_context = False
65        self.sequence_context = False
66        self.mapping_context = False
67        self.simple_key_context = False
68
69        # Characteristics of the last emitted character:
70        #  - current position.
71        #  - is it a whitespace?
72        #  - is it an indention character
73        #    (indentation space, '-', '?', or ':')?
74        self.line = 0
75        self.column = 0
76        self.whitespace = True
77        self.indention = True
78
79        # Whether the document requires an explicit document indicator
80        self.open_ended = False
81
82        # Formatting details.
83        self.canonical = canonical
84        self.allow_unicode = allow_unicode
85        self.best_indent = 2
86        if indent and 1 < indent < 10:
87            self.best_indent = indent
88        self.best_width = 80
89        if width and width > self.best_indent*2:
90            self.best_width = width
91        self.best_line_break = '\n'
92        if line_break in ['\r', '\n', '\r\n']:
93            self.best_line_break = line_break
94
95        # Tag prefixes.
96        self.tag_prefixes = None
97
98        # Prepared anchor and tag.
99        self.prepared_anchor = None
100        self.prepared_tag = None
101
102        # Scalar analysis and style.
103        self.analysis = None
104        self.style = None
105
106    def emit(self, event):
107        self.events.append(event)
108        while not self.need_more_events():
109            self.event = self.events.pop(0)
110            self.state()
111            self.event = None
112
113    # In some cases, we wait for a few next events before emitting.
114
115    def need_more_events(self):
116        if not self.events:
117            return True
118        event = self.events[0]
119        if isinstance(event, DocumentStartEvent):
120            return self.need_events(1)
121        elif isinstance(event, SequenceStartEvent):
122            return self.need_events(2)
123        elif isinstance(event, MappingStartEvent):
124            return self.need_events(3)
125        else:
126            return False
127
128    def need_events(self, count):
129        level = 0
130        for event in self.events[1:]:
131            if isinstance(event, (DocumentStartEvent, CollectionStartEvent)):
132                level += 1
133            elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)):
134                level -= 1
135            elif isinstance(event, StreamEndEvent):
136                level = -1
137            if level < 0:
138                return False
139        return (len(self.events) < count+1)
140
141    def increase_indent(self, flow=False, indentless=False):
142        self.indents.append(self.indent)
143        if self.indent is None:
144            if flow:
145                self.indent = self.best_indent
146            else:
147                self.indent = 0
148        elif not indentless:
149            self.indent += self.best_indent
150
151    # States.
152
153    # Stream handlers.
154
155    def expect_stream_start(self):
156        if isinstance(self.event, StreamStartEvent):
157            if self.event.encoding and not hasattr(self.stream, 'encoding'):
158                self.encoding = self.event.encoding
159            self.write_stream_start()
160            self.state = self.expect_first_document_start
161        else:
162            raise EmitterError("expected StreamStartEvent, but got %s"
163                    % self.event)
164
165    def expect_nothing(self):
166        raise EmitterError("expected nothing, but got %s" % self.event)
167
168    # Document handlers.
169
170    def expect_first_document_start(self):
171        return self.expect_document_start(first=True)
172
173    def expect_document_start(self, first=False):
174        if isinstance(self.event, DocumentStartEvent):
175            if (self.event.version or self.event.tags) and self.open_ended:
176                self.write_indicator('...', True)
177                self.write_indent()
178            if self.event.version:
179                version_text = self.prepare_version(self.event.version)
180                self.write_version_directive(version_text)
181            self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy()
182            if self.event.tags:
183                handles = self.event.tags.keys()
184                handles.sort()
185                for handle in handles:
186                    prefix = self.event.tags[handle]
187                    self.tag_prefixes[prefix] = handle
188                    handle_text = self.prepare_tag_handle(handle)
189                    prefix_text = self.prepare_tag_prefix(prefix)
190                    self.write_tag_directive(handle_text, prefix_text)
191            implicit = (first and not self.event.explicit and not self.canonical
192                    and not self.event.version and not self.event.tags
193                    and not self.check_empty_document())
194            if not implicit:
195                self.write_indent()
196                self.write_indicator('---', True)
197                if self.canonical:
198                    self.write_indent()
199            self.state = self.expect_document_root
200        elif isinstance(self.event, StreamEndEvent):
201            if self.open_ended:
202                self.write_indicator('...', True)
203                self.write_indent()
204            self.write_stream_end()
205            self.state = self.expect_nothing
206        else:
207            raise EmitterError("expected DocumentStartEvent, but got %s"
208                    % self.event)
209
210    def expect_document_end(self):
211        if isinstance(self.event, DocumentEndEvent):
212            self.write_indent()
213            if self.event.explicit:
214                self.write_indicator('...', True)
215                self.write_indent()
216            self.flush_stream()
217            self.state = self.expect_document_start
218        else:
219            raise EmitterError("expected DocumentEndEvent, but got %s"
220                    % self.event)
221
222    def expect_document_root(self):
223        self.states.append(self.expect_document_end)
224        self.expect_node(root=True)
225
226    # Node handlers.
227
228    def expect_node(self, root=False, sequence=False, mapping=False,
229            simple_key=False):
230        self.root_context = root
231        self.sequence_context = sequence
232        self.mapping_context = mapping
233        self.simple_key_context = simple_key
234        if isinstance(self.event, AliasEvent):
235            self.expect_alias()
236        elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)):
237            self.process_anchor('&')
238            self.process_tag()
239            if isinstance(self.event, ScalarEvent):
240                self.expect_scalar()
241            elif isinstance(self.event, SequenceStartEvent):
242                if self.flow_level or self.canonical or self.event.flow_style   \
243                        or self.check_empty_sequence():
244                    self.expect_flow_sequence()
245                else:
246                    self.expect_block_sequence()
247            elif isinstance(self.event, MappingStartEvent):
248                if self.flow_level or self.canonical or self.event.flow_style   \
249                        or self.check_empty_mapping():
250                    self.expect_flow_mapping()
251                else:
252                    self.expect_block_mapping()
253        else:
254            raise EmitterError("expected NodeEvent, but got %s" % self.event)
255
256    def expect_alias(self):
257        if self.event.anchor is None:
258            raise EmitterError("anchor is not specified for alias")
259        self.process_anchor('*')
260        self.state = self.states.pop()
261
262    def expect_scalar(self):
263        self.increase_indent(flow=True)
264        self.process_scalar()
265        self.indent = self.indents.pop()
266        self.state = self.states.pop()
267
268    # Flow sequence handlers.
269
270    def expect_flow_sequence(self):
271        self.write_indicator('[', True, whitespace=True)
272        self.flow_level += 1
273        self.increase_indent(flow=True)
274        self.state = self.expect_first_flow_sequence_item
275
276    def expect_first_flow_sequence_item(self):
277        if isinstance(self.event, SequenceEndEvent):
278            self.indent = self.indents.pop()
279            self.flow_level -= 1
280            self.write_indicator(']', False)
281            self.state = self.states.pop()
282        else:
283            if self.canonical or self.column > self.best_width:
284                self.write_indent()
285            self.states.append(self.expect_flow_sequence_item)
286            self.expect_node(sequence=True)
287
288    def expect_flow_sequence_item(self):
289        if isinstance(self.event, SequenceEndEvent):
290            self.indent = self.indents.pop()
291            self.flow_level -= 1
292            if self.canonical:
293                self.write_indicator(',', False)
294                self.write_indent()
295            self.write_indicator(']', False)
296            self.state = self.states.pop()
297        else:
298            self.write_indicator(',', False)
299            if self.canonical or self.column > self.best_width:
300                self.write_indent()
301            self.states.append(self.expect_flow_sequence_item)
302            self.expect_node(sequence=True)
303
304    # Flow mapping handlers.
305
306    def expect_flow_mapping(self):
307        self.write_indicator('{', True, whitespace=True)
308        self.flow_level += 1
309        self.increase_indent(flow=True)
310        self.state = self.expect_first_flow_mapping_key
311
312    def expect_first_flow_mapping_key(self):
313        if isinstance(self.event, MappingEndEvent):
314            self.indent = self.indents.pop()
315            self.flow_level -= 1
316            self.write_indicator('}', False)
317            self.state = self.states.pop()
318        else:
319            if self.canonical or self.column > self.best_width:
320                self.write_indent()
321            if not self.canonical and self.check_simple_key():
322                self.states.append(self.expect_flow_mapping_simple_value)
323                self.expect_node(mapping=True, simple_key=True)
324            else:
325                self.write_indicator('?', True)
326                self.states.append(self.expect_flow_mapping_value)
327                self.expect_node(mapping=True)
328
329    def expect_flow_mapping_key(self):
330        if isinstance(self.event, MappingEndEvent):
331            self.indent = self.indents.pop()
332            self.flow_level -= 1
333            if self.canonical:
334                self.write_indicator(',', False)
335                self.write_indent()
336            self.write_indicator('}', False)
337            self.state = self.states.pop()
338        else:
339            self.write_indicator(',', False)
340            if self.canonical or self.column > self.best_width:
341                self.write_indent()
342            if not self.canonical and self.check_simple_key():
343                self.states.append(self.expect_flow_mapping_simple_value)
344                self.expect_node(mapping=True, simple_key=True)
345            else:
346                self.write_indicator('?', True)
347                self.states.append(self.expect_flow_mapping_value)
348                self.expect_node(mapping=True)
349
350    def expect_flow_mapping_simple_value(self):
351        self.write_indicator(':', False)
352        self.states.append(self.expect_flow_mapping_key)
353        self.expect_node(mapping=True)
354
355    def expect_flow_mapping_value(self):
356        if self.canonical or self.column > self.best_width:
357            self.write_indent()
358        self.write_indicator(':', True)
359        self.states.append(self.expect_flow_mapping_key)
360        self.expect_node(mapping=True)
361
362    # Block sequence handlers.
363
364    def expect_block_sequence(self):
365        indentless = (self.mapping_context and not self.indention)
366        self.increase_indent(flow=False, indentless=indentless)
367        self.state = self.expect_first_block_sequence_item
368
369    def expect_first_block_sequence_item(self):
370        return self.expect_block_sequence_item(first=True)
371
372    def expect_block_sequence_item(self, first=False):
373        if not first and isinstance(self.event, SequenceEndEvent):
374            self.indent = self.indents.pop()
375            self.state = self.states.pop()
376        else:
377            self.write_indent()
378            self.write_indicator('-', True, indention=True)
379            self.states.append(self.expect_block_sequence_item)
380            self.expect_node(sequence=True)
381
382    # Block mapping handlers.
383
384    def expect_block_mapping(self):
385        self.increase_indent(flow=False)
386        self.state = self.expect_first_block_mapping_key
387
388    def expect_first_block_mapping_key(self):
389        return self.expect_block_mapping_key(first=True)
390
391    def expect_block_mapping_key(self, first=False):
392        if not first and isinstance(self.event, MappingEndEvent):
393            self.indent = self.indents.pop()
394            self.state = self.states.pop()
395        else:
396            self.write_indent()
397            if self.check_simple_key():
398                self.states.append(self.expect_block_mapping_simple_value)
399                self.expect_node(mapping=True, simple_key=True)
400            else:
401                self.write_indicator('?', True, indention=True)
402                self.states.append(self.expect_block_mapping_value)
403                self.expect_node(mapping=True)
404
405    def expect_block_mapping_simple_value(self):
406        self.write_indicator(':', False)
407        self.states.append(self.expect_block_mapping_key)
408        self.expect_node(mapping=True)
409
410    def expect_block_mapping_value(self):
411        self.write_indent()
412        self.write_indicator(':', True, indention=True)
413        self.states.append(self.expect_block_mapping_key)
414        self.expect_node(mapping=True)
415
416    # Checkers.
417
418    def check_empty_sequence(self):
419        return (isinstance(self.event, SequenceStartEvent) and self.events
420                and isinstance(self.events[0], SequenceEndEvent))
421
422    def check_empty_mapping(self):
423        return (isinstance(self.event, MappingStartEvent) and self.events
424                and isinstance(self.events[0], MappingEndEvent))
425
426    def check_empty_document(self):
427        if not isinstance(self.event, DocumentStartEvent) or not self.events:
428            return False
429        event = self.events[0]
430        return (isinstance(event, ScalarEvent) and event.anchor is None
431                and event.tag is None and event.implicit and event.value == '')
432
433    def check_simple_key(self):
434        length = 0
435        if isinstance(self.event, NodeEvent) and self.event.anchor is not None:
436            if self.prepared_anchor is None:
437                self.prepared_anchor = self.prepare_anchor(self.event.anchor)
438            length += len(self.prepared_anchor)
439        if isinstance(self.event, (ScalarEvent, CollectionStartEvent))  \
440                and self.event.tag is not None:
441            if self.prepared_tag is None:
442                self.prepared_tag = self.prepare_tag(self.event.tag)
443            length += len(self.prepared_tag)
444        if isinstance(self.event, ScalarEvent):
445            if self.analysis is None:
446                self.analysis = self.analyze_scalar(self.event.value)
447            length += len(self.analysis.scalar)
448        return (length < 128 and (isinstance(self.event, AliasEvent)
449            or (isinstance(self.event, ScalarEvent)
450                    and not self.analysis.empty and not self.analysis.multiline)
451            or self.check_empty_sequence() or self.check_empty_mapping()))
452
453    # Anchor, Tag, and Scalar processors.
454
455    def process_anchor(self, indicator):
456        if self.event.anchor is None:
457            self.prepared_anchor = None
458            return
459        if self.prepared_anchor is None:
460            self.prepared_anchor = self.prepare_anchor(self.event.anchor)
461        if self.prepared_anchor:
462            self.write_indicator(indicator+self.prepared_anchor, True)
463        self.prepared_anchor = None
464
465    def process_tag(self):
466        tag = self.event.tag
467        if isinstance(self.event, ScalarEvent):
468            if self.style is None:
469                self.style = self.choose_scalar_style()
470            if ((not self.canonical or tag is None) and
471                ((self.style == '' and self.event.implicit[0])
472                        or (self.style != '' and self.event.implicit[1]))):
473                self.prepared_tag = None
474                return
475            if self.event.implicit[0] and tag is None:
476                tag = '!'
477                self.prepared_tag = None
478        else:
479            if (not self.canonical or tag is None) and self.event.implicit:
480                self.prepared_tag = None
481                return
482        if tag is None:
483            raise EmitterError("tag is not specified")
484        if self.prepared_tag is None:
485            self.prepared_tag = self.prepare_tag(tag)
486        if self.prepared_tag:
487            self.write_indicator(self.prepared_tag, True)
488        self.prepared_tag = None
489
490    def choose_scalar_style(self):
491        if self.analysis is None:
492            self.analysis = self.analyze_scalar(self.event.value)
493        if self.event.style == '"' or self.canonical:
494            return '"'
495        if not self.event.style and self.event.implicit[0]:
496            if (not (self.simple_key_context and
497                    (self.analysis.empty or self.analysis.multiline))
498                and (self.flow_level and self.analysis.allow_flow_plain
499                    or (not self.flow_level and self.analysis.allow_block_plain))):
500                return ''
501        if self.event.style and self.event.style in '|>':
502            if (not self.flow_level and not self.simple_key_context
503                    and self.analysis.allow_block):
504                return self.event.style
505        if not self.event.style or self.event.style == '\'':
506            if (self.analysis.allow_single_quoted and
507                    not (self.simple_key_context and self.analysis.multiline)):
508                return '\''
509        return '"'
510
511    def process_scalar(self):
512        if self.analysis is None:
513            self.analysis = self.analyze_scalar(self.event.value)
514        if self.style is None:
515            self.style = self.choose_scalar_style()
516        split = (not self.simple_key_context)
517        #if self.analysis.multiline and split    \
518        #        and (not self.style or self.style in '\'\"'):
519        #    self.write_indent()
520        if self.style == '"':
521            self.write_double_quoted(self.analysis.scalar, split)
522        elif self.style == '\'':
523            self.write_single_quoted(self.analysis.scalar, split)
524        elif self.style == '>':
525            self.write_folded(self.analysis.scalar)
526        elif self.style == '|':
527            self.write_literal(self.analysis.scalar)
528        else:
529            self.write_plain(self.analysis.scalar, split)
530        self.analysis = None
531        self.style = None
532
533    # Analyzers.
534
535    def prepare_version(self, version):
536        major, minor = version
537        if major != 1:
538            raise EmitterError("unsupported YAML version: %d.%d" % (major, minor))
539        return '%d.%d' % (major, minor)
540
541    def prepare_tag_handle(self, handle):
542        if not handle:
543            raise EmitterError("tag handle must not be empty")
544        if handle[0] != '!' or handle[-1] != '!':
545            raise EmitterError("tag handle must start and end with '!': %r" % handle)
546        for ch in handle[1:-1]:
547            if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z'    \
548                    or ch in '-_'):
549                raise EmitterError("invalid character %r in the tag handle: %r"
550                        % (ch, handle))
551        return handle
552
553    def prepare_tag_prefix(self, prefix):
554        if not prefix:
555            raise EmitterError("tag prefix must not be empty")
556        chunks = []
557        start = end = 0
558        if prefix[0] == '!':
559            end = 1
560        while end < len(prefix):
561            ch = prefix[end]
562            if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
563                    or ch in '-;/?!:@&=+$,_.~*\'()[]':
564                end += 1
565            else:
566                if start < end:
567                    chunks.append(prefix[start:end])
568                start = end = end+1
569                data = ch.encode('utf-8')
570                for ch in data:
571                    chunks.append('%%%02X' % ord(ch))
572        if start < end:
573            chunks.append(prefix[start:end])
574        return ''.join(chunks)
575
576    def prepare_tag(self, tag):
577        if not tag:
578            raise EmitterError("tag must not be empty")
579        if tag == '!':
580            return tag
581        handle = None
582        suffix = tag
583        for prefix in self.tag_prefixes:
584            if tag.startswith(prefix)   \
585                    and (prefix == '!' or len(prefix) < len(tag)):
586                handle = self.tag_prefixes[prefix]
587                suffix = tag[len(prefix):]
588        chunks = []
589        start = end = 0
590        while end < len(suffix):
591            ch = suffix[end]
592            if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
593                    or ch in '-;/?:@&=+$,_.~*\'()[]'   \
594                    or (ch == '!' and handle != '!'):
595                end += 1
596            else:
597                if start < end:
598                    chunks.append(suffix[start:end])
599                start = end = end+1
600                data = ch.encode('utf-8')
601                for ch in data:
602                    chunks.append('%%%02X' % ord(ch))
603        if start < end:
604            chunks.append(suffix[start:end])
605        suffix_text = ''.join(chunks)
606        if handle:
607            return '%s%s' % (handle, suffix_text)
608        else:
609            return '!<%s>' % suffix_text
610
611    def prepare_anchor(self, anchor):
612        if not anchor:
613            raise EmitterError("anchor must not be empty")
614        for ch in anchor:
615            if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z'    \
616                    or ch in '-_'):
617                raise EmitterError("invalid character %r in the anchor: %r"
618                        % (ch, anchor))
619        return anchor
620
621    def analyze_scalar(self, scalar):
622
623        # Empty scalar is a special case.
624        if not scalar:
625            return ScalarAnalysis(scalar=scalar, empty=True, multiline=False,
626                    allow_flow_plain=False, allow_block_plain=True,
627                    allow_single_quoted=True, allow_double_quoted=True,
628                    allow_block=False)
629
630        # Indicators and special characters.
631        block_indicators = False
632        flow_indicators = False
633        line_breaks = False
634        special_characters = False
635
636        # Important whitespace combinations.
637        leading_space = False
638        leading_break = False
639        trailing_space = False
640        trailing_break = False
641        break_space = False
642        space_break = False
643
644        # Check document indicators.
645        if scalar.startswith('---') or scalar.startswith('...'):
646            block_indicators = True
647            flow_indicators = True
648
649        # First character or preceded by a whitespace.
650        preceeded_by_whitespace = True
651
652        # Last character or followed by a whitespace.
653        followed_by_whitespace = (len(scalar) == 1 or
654                scalar[1] in '\0 \t\r\n\x85\u2028\u2029')
655
656        # The previous character is a space.
657        previous_space = False
658
659        # The previous character is a break.
660        previous_break = False
661
662        index = 0
663        while index < len(scalar):
664            ch = scalar[index]
665
666            # Check for indicators.
667            if index == 0:
668                # Leading indicators are special characters.
669                if ch in '#,[]{}&*!|>\'\"%@`': 
670                    flow_indicators = True
671                    block_indicators = True
672                if ch in '?:':
673                    flow_indicators = True
674                    if followed_by_whitespace:
675                        block_indicators = True
676                if ch == '-' and followed_by_whitespace:
677                    flow_indicators = True
678                    block_indicators = True
679            else:
680                # Some indicators cannot appear within a scalar as well.
681                if ch in ',?[]{}':
682                    flow_indicators = True
683                if ch == ':':
684                    flow_indicators = True
685                    if followed_by_whitespace:
686                        block_indicators = True
687                if ch == '#' and preceeded_by_whitespace:
688                    flow_indicators = True
689                    block_indicators = True
690
691            # Check for line breaks, special, and unicode characters.
692            if ch in '\n\x85\u2028\u2029':
693                line_breaks = True
694            if not (ch == '\n' or '\x20' <= ch <= '\x7E'):
695                if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF'
696                        or '\uE000' <= ch <= '\uFFFD') and ch != '\uFEFF':
697                    unicode_characters = True
698                    if not self.allow_unicode:
699                        special_characters = True
700                else:
701                    special_characters = True
702
703            # Detect important whitespace combinations.
704            if ch == ' ':
705                if index == 0:
706                    leading_space = True
707                if index == len(scalar)-1:
708                    trailing_space = True
709                if previous_break:
710                    break_space = True
711                previous_space = True
712                previous_break = False
713            elif ch in '\n\x85\u2028\u2029':
714                if index == 0:
715                    leading_break = True
716                if index == len(scalar)-1:
717                    trailing_break = True
718                if previous_space:
719                    space_break = True
720                previous_space = False
721                previous_break = True
722            else:
723                previous_space = False
724                previous_break = False
725
726            # Prepare for the next character.
727            index += 1
728            preceeded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029')
729            followed_by_whitespace = (index+1 >= len(scalar) or
730                    scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029')
731
732        # Let's decide what styles are allowed.
733        allow_flow_plain = True
734        allow_block_plain = True
735        allow_single_quoted = True
736        allow_double_quoted = True
737        allow_block = True
738
739        # Leading and trailing whitespaces are bad for plain scalars.
740        if (leading_space or leading_break
741                or trailing_space or trailing_break):
742            allow_flow_plain = allow_block_plain = False
743
744        # We do not permit trailing spaces for block scalars.
745        if trailing_space:
746            allow_block = False
747
748        # Spaces at the beginning of a new line are only acceptable for block
749        # scalars.
750        if break_space:
751            allow_flow_plain = allow_block_plain = allow_single_quoted = False
752
753        # Spaces followed by breaks, as well as special character are only
754        # allowed for double quoted scalars.
755        if space_break or special_characters:
756            allow_flow_plain = allow_block_plain =  \
757            allow_single_quoted = allow_block = False
758
759        # Although the plain scalar writer supports breaks, we never emit
760        # multiline plain scalars.
761        if line_breaks:
762            allow_flow_plain = allow_block_plain = False
763
764        # Flow indicators are forbidden for flow plain scalars.
765        if flow_indicators:
766            allow_flow_plain = False
767
768        # Block indicators are forbidden for block plain scalars.
769        if block_indicators:
770            allow_block_plain = False
771
772        return ScalarAnalysis(scalar=scalar,
773                empty=False, multiline=line_breaks,
774                allow_flow_plain=allow_flow_plain,
775                allow_block_plain=allow_block_plain,
776                allow_single_quoted=allow_single_quoted,
777                allow_double_quoted=allow_double_quoted,
778                allow_block=allow_block)
779
780    # Writers.
781
782    def flush_stream(self):
783        if hasattr(self.stream, 'flush'):
784            self.stream.flush()
785
786    def write_stream_start(self):
787        # Write BOM if needed.
788        if self.encoding and self.encoding.startswith('utf-16'):
789            self.stream.write('\xFF\xFE'.encode(self.encoding))
790
791    def write_stream_end(self):
792        self.flush_stream()
793
794    def write_indicator(self, indicator, need_whitespace,
795            whitespace=False, indention=False):
796        if self.whitespace or not need_whitespace:
797            data = indicator
798        else:
799            data = ' '+indicator
800        self.whitespace = whitespace
801        self.indention = self.indention and indention
802        self.column += len(data)
803        self.open_ended = False
804        if self.encoding:
805            data = data.encode(self.encoding)
806        self.stream.write(data)
807
808    def write_indent(self):
809        indent = self.indent or 0
810        if not self.indention or self.column > indent   \
811                or (self.column == indent and not self.whitespace):
812            self.write_line_break()
813        if self.column < indent:
814            self.whitespace = True
815            data = ' '*(indent-self.column)
816            self.column = indent
817            if self.encoding:
818                data = data.encode(self.encoding)
819            self.stream.write(data)
820
821    def write_line_break(self, data=None):
822        if data is None:
823            data = self.best_line_break
824        self.whitespace = True
825        self.indention = True
826        self.line += 1
827        self.column = 0
828        if self.encoding:
829            data = data.encode(self.encoding)
830        self.stream.write(data)
831
832    def write_version_directive(self, version_text):
833        data = '%%YAML %s' % version_text
834        if self.encoding:
835            data = data.encode(self.encoding)
836        self.stream.write(data)
837        self.write_line_break()
838
839    def write_tag_directive(self, handle_text, prefix_text):
840        data = '%%TAG %s %s' % (handle_text, prefix_text)
841        if self.encoding:
842            data = data.encode(self.encoding)
843        self.stream.write(data)
844        self.write_line_break()
845
846    # Scalar streams.
847
848    def write_single_quoted(self, text, split=True):
849        self.write_indicator('\'', True)
850        spaces = False
851        breaks = False
852        start = end = 0
853        while end <= len(text):
854            ch = None
855            if end < len(text):
856                ch = text[end]
857            if spaces:
858                if ch is None or ch != ' ':
859                    if start+1 == end and self.column > self.best_width and split   \
860                            and start != 0 and end != len(text):
861                        self.write_indent()
862                    else:
863                        data = text[start:end]
864                        self.column += len(data)
865                        if self.encoding:
866                            data = data.encode(self.encoding)
867                        self.stream.write(data)
868                    start = end
869            elif breaks:
870                if ch is None or ch not in '\n\x85\u2028\u2029':
871                    if text[start] == '\n':
872                        self.write_line_break()
873                    for br in text[start:end]:
874                        if br == '\n':
875                            self.write_line_break()
876                        else:
877                            self.write_line_break(br)
878                    self.write_indent()
879                    start = end
880            else:
881                if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'':
882                    if start < end:
883                        data = text[start:end]
884                        self.column += len(data)
885                        if self.encoding:
886                            data = data.encode(self.encoding)
887                        self.stream.write(data)
888                        start = end
889            if ch == '\'':
890                data = '\'\''
891                self.column += 2
892                if self.encoding:
893                    data = data.encode(self.encoding)
894                self.stream.write(data)
895                start = end + 1
896            if ch is not None:
897                spaces = (ch == ' ')
898                breaks = (ch in '\n\x85\u2028\u2029')
899            end += 1
900        self.write_indicator('\'', False)
901
902    ESCAPE_REPLACEMENTS = {
903        '\0':       '0',
904        '\x07':     'a',
905        '\x08':     'b',
906        '\x09':     't',
907        '\x0A':     'n',
908        '\x0B':     'v',
909        '\x0C':     'f',
910        '\x0D':     'r',
911        '\x1B':     'e',
912        '\"':       '\"',
913        '\\':       '\\',
914        '\x85':     'N',
915        '\xA0':     '_',
916        '\u2028':   'L',
917        '\u2029':   'P',
918    }
919
920    def write_double_quoted(self, text, split=True):
921        self.write_indicator('"', True)
922        start = end = 0
923        while end <= len(text):
924            ch = None
925            if end < len(text):
926                ch = text[end]
927            if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \
928                    or not ('\x20' <= ch <= '\x7E'
929                        or (self.allow_unicode
930                            and ('\xA0' <= ch <= '\uD7FF'
931                                or '\uE000' <= ch <= '\uFFFD'))):
932                if start < end:
933                    data = text[start:end]
934                    self.column += len(data)
935                    if self.encoding:
936                        data = data.encode(self.encoding)
937                    self.stream.write(data)
938                    start = end
939                if ch is not None:
940                    if ch in self.ESCAPE_REPLACEMENTS:
941                        data = '\\'+self.ESCAPE_REPLACEMENTS[ch]
942                    elif ch <= '\xFF':
943                        data = '\\x%02X' % ord(ch)
944                    elif ch <= '\uFFFF':
945                        data = '\\u%04X' % ord(ch)
946                    else:
947                        data = '\\U%08X' % ord(ch)
948                    self.column += len(data)
949                    if self.encoding:
950                        data = data.encode(self.encoding)
951                    self.stream.write(data)
952                    start = end+1
953            if 0 < end < len(text)-1 and (ch == ' ' or start >= end)    \
954                    and self.column+(end-start) > self.best_width and split:
955                data = text[start:end]+'\\'
956                if start < end:
957                    start = end
958                self.column += len(data)
959                if self.encoding:
960                    data = data.encode(self.encoding)
961                self.stream.write(data)
962                self.write_indent()
963                self.whitespace = False
964                self.indention = False
965                if text[start] == ' ':
966                    data = '\\'
967                    self.column += len(data)
968                    if self.encoding:
969                        data = data.encode(self.encoding)
970                    self.stream.write(data)
971            end += 1
972        self.write_indicator('"', False)
973
974    def determine_block_hints(self, text):
975        hints = ''
976        if text:
977            if text[0] in ' \n\x85\u2028\u2029':
978                hints += str(self.best_indent)
979            if text[-1] not in '\n\x85\u2028\u2029':
980                hints += '-'
981            elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029':
982                hints += '+'
983        return hints
984
985    def write_folded(self, text):
986        hints = self.determine_block_hints(text)
987        self.write_indicator('>'+hints, True)
988        if hints[-1:] == '+':
989            self.open_ended = True
990        self.write_line_break()
991        leading_space = True
992        spaces = False
993        breaks = True
994        start = end = 0
995        while end <= len(text):
996            ch = None
997            if end < len(text):
998                ch = text[end]
999            if breaks:
1000                if ch is None or ch not in '\n\x85\u2028\u2029':
1001                    if not leading_space and ch is not None and ch != ' '   \
1002                            and text[start] == '\n':
1003                        self.write_line_break()
1004                    leading_space = (ch == ' ')
1005                    for br in text[start:end]:
1006                        if br == '\n':
1007                            self.write_line_break()
1008                        else:
1009                            self.write_line_break(br)
1010                    if ch is not None:
1011                        self.write_indent()
1012                    start = end
1013            elif spaces:
1014                if ch != ' ':
1015                    if start+1 == end and self.column > self.best_width:
1016                        self.write_indent()
1017                    else:
1018                        data = text[start:end]
1019                        self.column += len(data)
1020                        if self.encoding:
1021                            data = data.encode(self.encoding)
1022                        self.stream.write(data)
1023                    start = end
1024            else:
1025                if ch is None or ch in ' \n\x85\u2028\u2029':
1026                    data = text[start:end]
1027                    if self.encoding:
1028                        data = data.encode(self.encoding)
1029                    self.stream.write(data)
1030                    if ch is None:
1031                        self.write_line_break()
1032                    start = end
1033            if ch is not None:
1034                breaks = (ch in '\n\x85\u2028\u2029')
1035                spaces = (ch == ' ')
1036            end += 1
1037
1038    def write_literal(self, text):
1039        hints = self.determine_block_hints(text)
1040        self.write_indicator('|'+hints, True)
1041        if hints[-1:] == '+':
1042            self.open_ended = True
1043        self.write_line_break()
1044        breaks = True
1045        start = end = 0
1046        while end <= len(text):
1047            ch = None
1048            if end < len(text):
1049                ch = text[end]
1050            if breaks:
1051                if ch is None or ch not in '\n\x85\u2028\u2029':
1052                    for br in text[start:end]:
1053                        if br == '\n':
1054                            self.write_line_break()
1055                        else:
1056                            self.write_line_break(br)
1057                    if ch is not None:
1058                        self.write_indent()
1059                    start = end
1060            else:
1061                if ch is None or ch in '\n\x85\u2028\u2029':
1062                    data = text[start:end]
1063                    if self.encoding:
1064                        data = data.encode(self.encoding)
1065                    self.stream.write(data)
1066                    if ch is None:
1067                        self.write_line_break()
1068                    start = end
1069            if ch is not None:
1070                breaks = (ch in '\n\x85\u2028\u2029')
1071            end += 1
1072
1073    def write_plain(self, text, split=True):
1074        if self.root_context:
1075            self.open_ended = True
1076        if not text:
1077            return
1078        if not self.whitespace:
1079            data = ' '
1080            self.column += len(data)
1081            if self.encoding:
1082                data = data.encode(self.encoding)
1083            self.stream.write(data)
1084        self.whitespace = False
1085        self.indention = False
1086        spaces = False
1087        breaks = False
1088        start = end = 0
1089        while end <= len(text):
1090            ch = None
1091            if end < len(text):
1092                ch = text[end]
1093            if spaces:
1094                if ch != ' ':
1095                    if start+1 == end and self.column > self.best_width and split:
1096                        self.write_indent()
1097                        self.whitespace = False
1098                        self.indention = False
1099                    else:
1100                        data = text[start:end]
1101                        self.column += len(data)
1102                        if self.encoding:
1103                            data = data.encode(self.encoding)
1104                        self.stream.write(data)
1105                    start = end
1106            elif breaks:
1107                if ch not in '\n\x85\u2028\u2029':
1108                    if text[start] == '\n':
1109                        self.write_line_break()
1110                    for br in text[start:end]:
1111                        if br == '\n':
1112                            self.write_line_break()
1113                        else:
1114                            self.write_line_break(br)
1115                    self.write_indent()
1116                    self.whitespace = False
1117                    self.indention = False
1118                    start = end
1119            else:
1120                if ch is None or ch in ' \n\x85\u2028\u2029':
1121                    data = text[start:end]
1122                    self.column += len(data)
1123                    if self.encoding:
1124                        data = data.encode(self.encoding)
1125                    self.stream.write(data)
1126                    start = end
1127            if ch is not None:
1128                spaces = (ch == ' ')
1129                breaks = (ch in '\n\x85\u2028\u2029')
1130            end += 1
1131
Note: See TracBrowser for help on using the repository browser.