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

Revision 132, 40.4 KB checked in by xi, 8 years ago (diff)

Emitter is done!!!

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