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