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

Revision 308, 41.4 KB checked in by xi, 6 years ago (diff)

Refactored whitespace combination detector in the scalar analyzer: support dumping ' <break> <space> ...' in the block style.

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