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

Revision 345, 42.1 KB checked in by xi, 5 years ago (diff)

Added a workaround against #116 (Thanks Andrey Somov).

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