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

Revision 329, 41.7 KB checked in by xi, 5 years ago (diff)

Fixed the remaining Python 3 compatibility issues.

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