source: pyyaml/trunk/lib/yaml/emitter.py @ 299

Revision 299, 43.3 KB checked in by xi, 5 years ago (diff)

Permit emitting block scalars with leading spaces or breaks.

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