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