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
9 __all__ = ['Emitter', 'EmitterError']
11 from .error import YAMLError
14 class EmitterError(YAMLError):
18 def __init__(self, scalar, empty, multiline,
19 allow_flow_plain, allow_block_plain,
20 allow_single_quoted, allow_double_quoted,
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
33 DEFAULT_TAG_PREFIXES = {
35 'tag:yaml.org,2002:' : '!!',
38 def __init__(self, stream, canonical=None, indent=None, width=None,
39 allow_unicode=None, line_break=None):
41 # The stream should have the methods `write` and possibly `flush`.
44 # Encoding can be overriden by STREAM-START.
47 # Emitter is a state machine with a stack of states to handle nested
50 self.state = self.expect_stream_start
52 # Current event and the event queue.
56 # The current indentation level and the stack of previous indents.
64 self.root_context = False
65 self.sequence_context = False
66 self.mapping_context = False
67 self.simple_key_context = False
69 # Characteristics of the last emitted character:
71 # - is it a whitespace?
72 # - is it an indention character
73 # (indentation space, '-', '?', or ':')?
76 self.whitespace = True
79 # Whether the document requires an explicit document indicator
80 self.open_ended = False
83 self.canonical = canonical
84 self.allow_unicode = allow_unicode
86 if indent and 1 < indent < 10:
87 self.best_indent = indent
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
96 self.tag_prefixes = None
98 # Prepared anchor and tag.
99 self.prepared_anchor = None
100 self.prepared_tag = None
102 # Scalar analysis and style.
107 # Reset the state attributes (to clear self-references)
111 def emit(self, event):
112 self.events.append(event)
113 while not self.need_more_events():
114 self.event = self.events.pop(0)
118 # In some cases, we wait for a few next events before emitting.
120 def need_more_events(self):
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)
133 def need_events(self, count):
135 for event in self.events[1:]:
136 if isinstance(event, (DocumentStartEvent, CollectionStartEvent)):
138 elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)):
140 elif isinstance(event, StreamEndEvent):
144 return (len(self.events) < count+1)
146 def increase_indent(self, flow=False, indentless=False):
147 self.indents.append(self.indent)
148 if self.indent is None:
150 self.indent = self.best_indent
154 self.indent += self.best_indent
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
167 raise EmitterError("expected StreamStartEvent, but got %s"
170 def expect_nothing(self):
171 raise EmitterError("expected nothing, but got %s" % self.event)
175 def expect_first_document_start(self):
176 return self.expect_document_start(first=True)
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)
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()
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())
200 self.write_indicator('---', True)
203 self.state = self.expect_document_root
204 elif isinstance(self.event, StreamEndEvent):
206 self.write_indicator('...', True)
208 self.write_stream_end()
209 self.state = self.expect_nothing
211 raise EmitterError("expected DocumentStartEvent, but got %s"
214 def expect_document_end(self):
215 if isinstance(self.event, DocumentEndEvent):
217 if self.event.explicit:
218 self.write_indicator('...', True)
221 self.state = self.expect_document_start
223 raise EmitterError("expected DocumentEndEvent, but got %s"
226 def expect_document_root(self):
227 self.states.append(self.expect_document_end)
228 self.expect_node(root=True)
232 def expect_node(self, root=False, sequence=False, mapping=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):
240 elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)):
241 self.process_anchor('&')
243 if isinstance(self.event, ScalarEvent):
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()
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()
256 self.expect_block_mapping()
258 raise EmitterError("expected NodeEvent, but got %s" % self.event)
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()
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()
272 # Flow sequence handlers.
274 def expect_flow_sequence(self):
275 self.write_indicator('[', True, whitespace=True)
277 self.increase_indent(flow=True)
278 self.state = self.expect_first_flow_sequence_item
280 def expect_first_flow_sequence_item(self):
281 if isinstance(self.event, SequenceEndEvent):
282 self.indent = self.indents.pop()
284 self.write_indicator(']', False)
285 self.state = self.states.pop()
287 if self.canonical or self.column > self.best_width:
289 self.states.append(self.expect_flow_sequence_item)
290 self.expect_node(sequence=True)
292 def expect_flow_sequence_item(self):
293 if isinstance(self.event, SequenceEndEvent):
294 self.indent = self.indents.pop()
297 self.write_indicator(',', False)
299 self.write_indicator(']', False)
300 self.state = self.states.pop()
302 self.write_indicator(',', False)
303 if self.canonical or self.column > self.best_width:
305 self.states.append(self.expect_flow_sequence_item)
306 self.expect_node(sequence=True)
308 # Flow mapping handlers.
310 def expect_flow_mapping(self):
311 self.write_indicator('{', True, whitespace=True)
313 self.increase_indent(flow=True)
314 self.state = self.expect_first_flow_mapping_key
316 def expect_first_flow_mapping_key(self):
317 if isinstance(self.event, MappingEndEvent):
318 self.indent = self.indents.pop()
320 self.write_indicator('}', False)
321 self.state = self.states.pop()
323 if self.canonical or self.column > self.best_width:
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)
329 self.write_indicator('?', True)
330 self.states.append(self.expect_flow_mapping_value)
331 self.expect_node(mapping=True)
333 def expect_flow_mapping_key(self):
334 if isinstance(self.event, MappingEndEvent):
335 self.indent = self.indents.pop()
338 self.write_indicator(',', False)
340 self.write_indicator('}', False)
341 self.state = self.states.pop()
343 self.write_indicator(',', False)
344 if self.canonical or self.column > self.best_width:
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)
350 self.write_indicator('?', True)
351 self.states.append(self.expect_flow_mapping_value)
352 self.expect_node(mapping=True)
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)
359 def expect_flow_mapping_value(self):
360 if self.canonical or self.column > self.best_width:
362 self.write_indicator(':', True)
363 self.states.append(self.expect_flow_mapping_key)
364 self.expect_node(mapping=True)
366 # Block sequence handlers.
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
373 def expect_first_block_sequence_item(self):
374 return self.expect_block_sequence_item(first=True)
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()
382 self.write_indicator('-', True, indention=True)
383 self.states.append(self.expect_block_sequence_item)
384 self.expect_node(sequence=True)
386 # Block mapping handlers.
388 def expect_block_mapping(self):
389 self.increase_indent(flow=False)
390 self.state = self.expect_first_block_mapping_key
392 def expect_first_block_mapping_key(self):
393 return self.expect_block_mapping_key(first=True)
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()
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)
405 self.write_indicator('?', True, indention=True)
406 self.states.append(self.expect_block_mapping_value)
407 self.expect_node(mapping=True)
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)
414 def expect_block_mapping_value(self):
416 self.write_indicator(':', True, indention=True)
417 self.states.append(self.expect_block_mapping_key)
418 self.expect_node(mapping=True)
422 def check_empty_sequence(self):
423 return (isinstance(self.event, SequenceStartEvent) and self.events
424 and isinstance(self.events[0], SequenceEndEvent))
426 def check_empty_mapping(self):
427 return (isinstance(self.event, MappingStartEvent) and self.events
428 and isinstance(self.events[0], MappingEndEvent))
430 def check_empty_document(self):
431 if not isinstance(self.event, DocumentStartEvent) or not self.events:
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 == '')
437 def check_simple_key(self):
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()))
457 # Anchor, Tag, and Scalar processors.
459 def process_anchor(self, indicator):
460 if self.event.anchor is None:
461 self.prepared_anchor = None
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
469 def process_tag(self):
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
479 if self.event.implicit[0] and tag is None:
481 self.prepared_tag = None
483 if (not self.canonical or tag is None) and self.event.implicit:
484 self.prepared_tag = 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
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:
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))):
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)):
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)
533 self.write_plain(self.analysis.scalar, split)
539 def prepare_version(self, version):
540 major, minor = version
542 raise EmitterError("unsupported YAML version: %d.%d" % (major, minor))
543 return '%d.%d' % (major, minor)
545 def prepare_tag_handle(self, 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' \
553 raise EmitterError("invalid character %r in the tag handle: %r"
557 def prepare_tag_prefix(self, prefix):
559 raise EmitterError("tag prefix must not be empty")
564 while end < len(prefix):
566 if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
567 or ch in '-;/?!:@&=+$,_.~*\'()[]':
571 chunks.append(prefix[start:end])
573 data = ch.encode('utf-8')
575 chunks.append('%%%02X' % ord(ch))
577 chunks.append(prefix[start:end])
578 return ''.join(chunks)
580 def prepare_tag(self, tag):
582 raise EmitterError("tag must not be empty")
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):]
595 while end < len(suffix):
597 if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
598 or ch in '-;/?:@&=+$,_.~*\'()[]' \
599 or (ch == '!' and handle != '!'):
603 chunks.append(suffix[start:end])
605 data = ch.encode('utf-8')
607 chunks.append('%%%02X' % ord(ch))
609 chunks.append(suffix[start:end])
610 suffix_text = ''.join(chunks)
612 return '%s%s' % (handle, suffix_text)
614 return '!<%s>' % suffix_text
616 def prepare_anchor(self, anchor):
618 raise EmitterError("anchor must not be empty")
620 if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
622 raise EmitterError("invalid character %r in the anchor: %r"
626 def analyze_scalar(self, scalar):
628 # Empty scalar is a special case.
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,
635 # Indicators and special characters.
636 block_indicators = False
637 flow_indicators = False
639 special_characters = False
641 # Important whitespace combinations.
642 leading_space = False
643 leading_break = False
644 trailing_space = False
645 trailing_break = False
649 # Check document indicators.
650 if scalar.startswith('---') or scalar.startswith('...'):
651 block_indicators = True
652 flow_indicators = True
654 # First character or preceded by a whitespace.
655 preceeded_by_whitespace = True
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')
661 # The previous character is a space.
662 previous_space = False
664 # The previous character is a break.
665 previous_break = False
668 while index < len(scalar):
671 # Check for indicators.
673 # Leading indicators are special characters.
674 if ch in '#,[]{}&*!|>\'\"%@`':
675 flow_indicators = True
676 block_indicators = True
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
685 # Some indicators cannot appear within a scalar as well.
687 flow_indicators = True
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
696 # Check for line breaks, special, and unicode characters.
697 if ch in '\n\x85\u2028\u2029':
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
706 special_characters = True
708 # Detect important whitespace combinations.
712 if index == len(scalar)-1:
713 trailing_space = True
716 previous_space = True
717 previous_break = False
718 elif ch in '\n\x85\u2028\u2029':
721 if index == len(scalar)-1:
722 trailing_break = True
725 previous_space = False
726 previous_break = True
728 previous_space = False
729 previous_break = False
731 # Prepare for the next character.
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')
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
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
749 # We do not permit trailing spaces for block scalars.
753 # Spaces at the beginning of a new line are only acceptable for block
756 allow_flow_plain = allow_block_plain = allow_single_quoted = False
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
764 # Although the plain scalar writer supports breaks, we never emit
765 # multiline plain scalars.
767 allow_flow_plain = allow_block_plain = False
769 # Flow indicators are forbidden for flow plain scalars.
771 allow_flow_plain = False
773 # Block indicators are forbidden for block plain scalars.
775 allow_block_plain = False
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)
787 def flush_stream(self):
788 if hasattr(self.stream, 'flush'):
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))
796 def write_stream_end(self):
799 def write_indicator(self, indicator, need_whitespace,
800 whitespace=False, indention=False):
801 if self.whitespace or not need_whitespace:
805 self.whitespace = whitespace
806 self.indention = self.indention and indention
807 self.column += len(data)
808 self.open_ended = False
810 data = data.encode(self.encoding)
811 self.stream.write(data)
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)
823 data = data.encode(self.encoding)
824 self.stream.write(data)
826 def write_line_break(self, data=None):
828 data = self.best_line_break
829 self.whitespace = True
830 self.indention = True
834 data = data.encode(self.encoding)
835 self.stream.write(data)
837 def write_version_directive(self, version_text):
838 data = '%%YAML %s' % version_text
840 data = data.encode(self.encoding)
841 self.stream.write(data)
842 self.write_line_break()
844 def write_tag_directive(self, handle_text, prefix_text):
845 data = '%%TAG %s %s' % (handle_text, prefix_text)
847 data = data.encode(self.encoding)
848 self.stream.write(data)
849 self.write_line_break()
853 def write_single_quoted(self, text, split=True):
854 self.write_indicator('\'', True)
858 while end <= len(text):
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):
868 data = text[start:end]
869 self.column += len(data)
871 data = data.encode(self.encoding)
872 self.stream.write(data)
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]:
880 self.write_line_break()
882 self.write_line_break(br)
886 if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'':
888 data = text[start:end]
889 self.column += len(data)
891 data = data.encode(self.encoding)
892 self.stream.write(data)
898 data = data.encode(self.encoding)
899 self.stream.write(data)
903 breaks = (ch in '\n\x85\u2028\u2029')
905 self.write_indicator('\'', False)
907 ESCAPE_REPLACEMENTS = {
925 def write_double_quoted(self, text, split=True):
926 self.write_indicator('"', True)
928 while end <= len(text):
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'))):
938 data = text[start:end]
939 self.column += len(data)
941 data = data.encode(self.encoding)
942 self.stream.write(data)
945 if ch in self.ESCAPE_REPLACEMENTS:
946 data = '\\'+self.ESCAPE_REPLACEMENTS[ch]
948 data = '\\x%02X' % ord(ch)
950 data = '\\u%04X' % ord(ch)
952 data = '\\U%08X' % ord(ch)
953 self.column += len(data)
955 data = data.encode(self.encoding)
956 self.stream.write(data)
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]+'\\'
963 self.column += len(data)
965 data = data.encode(self.encoding)
966 self.stream.write(data)
968 self.whitespace = False
969 self.indention = False
970 if text[start] == ' ':
972 self.column += len(data)
974 data = data.encode(self.encoding)
975 self.stream.write(data)
977 self.write_indicator('"', False)
979 def determine_block_hints(self, 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':
986 elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029':
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()
1000 while end <= len(text):
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]:
1012 self.write_line_break()
1014 self.write_line_break(br)
1020 if start+1 == end and self.column > self.best_width:
1023 data = text[start:end]
1024 self.column += len(data)
1026 data = data.encode(self.encoding)
1027 self.stream.write(data)
1030 if ch is None or ch in ' \n\x85\u2028\u2029':
1031 data = text[start:end]
1032 self.column += len(data)
1034 data = data.encode(self.encoding)
1035 self.stream.write(data)
1037 self.write_line_break()
1040 breaks = (ch in '\n\x85\u2028\u2029')
1041 spaces = (ch == ' ')
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()
1052 while end <= len(text):
1057 if ch is None or ch not in '\n\x85\u2028\u2029':
1058 for br in text[start:end]:
1060 self.write_line_break()
1062 self.write_line_break(br)
1067 if ch is None or ch in '\n\x85\u2028\u2029':
1068 data = text[start:end]
1070 data = data.encode(self.encoding)
1071 self.stream.write(data)
1073 self.write_line_break()
1076 breaks = (ch in '\n\x85\u2028\u2029')
1079 def write_plain(self, text, split=True):
1080 if self.root_context:
1081 self.open_ended = True
1084 if not self.whitespace:
1086 self.column += len(data)
1088 data = data.encode(self.encoding)
1089 self.stream.write(data)
1090 self.whitespace = False
1091 self.indention = False
1095 while end <= len(text):
1101 if start+1 == end and self.column > self.best_width and split:
1103 self.whitespace = False
1104 self.indention = False
1106 data = text[start:end]
1107 self.column += len(data)
1109 data = data.encode(self.encoding)
1110 self.stream.write(data)
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]:
1118 self.write_line_break()
1120 self.write_line_break(br)
1122 self.whitespace = False
1123 self.indention = False
1126 if ch is None or ch in ' \n\x85\u2028\u2029':
1127 data = text[start:end]
1128 self.column += len(data)
1130 data = data.encode(self.encoding)
1131 self.stream.write(data)
1134 spaces = (ch == ' ')
1135 breaks = (ch in '\n\x85\u2028\u2029')