1 //          Copyright Ferdinand Majerech 2011.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 /**
7  * YAML emitter.
8  * Code based on PyYAML: http://www.pyyaml.org
9  */
10 module dyaml.emitter;
11 
12 
13 import std.algorithm;
14 import std.array;
15 import std.ascii;
16 import std.conv;
17 import std.encoding;
18 import std.exception;
19 import std.format;
20 import std.range;
21 import std.string;
22 import std.system;
23 import std.typecons;
24 import std.utf;
25 
26 import dyaml.encoding;
27 import dyaml.escapes;
28 import dyaml.event;
29 import dyaml.exception;
30 import dyaml.linebreak;
31 import dyaml.queue;
32 import dyaml.scanner;
33 import dyaml.style;
34 import dyaml.tagdirective;
35 
36 
37 package:
38 
39 //Stores results of analysis of a scalar, determining e.g. what scalar style to use.
40 struct ScalarAnalysis
41 {
42     //Scalar itself.
43     string scalar;
44 
45     enum AnalysisFlags
46     {
47         empty = 1<<0,
48         multiline = 1<<1,
49         allowFlowPlain = 1<<2,
50         allowBlockPlain = 1<<3,
51         allowSingleQuoted = 1<<4,
52         allowDoubleQuoted = 1<<5,
53         allowBlock = 1<<6,
54         isNull = 1<<7
55     }
56 
57     ///Analysis results.
58     BitFlags!AnalysisFlags flags;
59 }
60 
61 private alias isNewLine = among!('\n', '\u0085', '\u2028', '\u2029');
62 
63 private alias isSpecialChar = among!('#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\\', '\'', '"', '%', '@', '`');
64 
65 private alias isFlowIndicator = among!(',', '?', '[', ']', '{', '}');
66 
67 private alias isSpace = among!('\0', '\n', '\r', '\u0085', '\u2028', '\u2029', ' ', '\t');
68 
69 //Emits YAML events into a file/stream.
70 struct Emitter(Range, CharType) if (isOutputRange!(Range, CharType))
71 {
72     private:
73         ///Default tag handle shortcuts and replacements.
74         static TagDirective[] defaultTagDirectives_ =
75             [TagDirective("!", "!"), TagDirective("!!", "tag:yaml.org,2002:")];
76 
77         ///Stream to write to.
78         Range stream_;
79 
80         /// Type used for upcoming emitter steps
81         alias EmitterFunction = void function(scope typeof(this)*) @safe;
82 
83         ///Stack of states.
84         Appender!(EmitterFunction[]) states_;
85 
86         ///Current state.
87         EmitterFunction state_;
88 
89         ///Event queue.
90         Queue!Event events_;
91         ///Event we're currently emitting.
92         Event event_;
93 
94         ///Stack of previous indentation levels.
95         Appender!(int[]) indents_;
96         ///Current indentation level.
97         int indent_ = -1;
98 
99         ///Level of nesting in flow context. If 0, we're in block context.
100         uint flowLevel_ = 0;
101 
102         /// Describes context (where we are in the document).
103         enum Context
104         {
105             /// Root node of a document.
106             root,
107             /// Sequence.
108             sequence,
109             /// Mapping.
110             mappingNoSimpleKey,
111             /// Mapping, in a simple key.
112             mappingSimpleKey,
113         }
114         /// Current context.
115         Context context_;
116 
117         ///Characteristics of the last emitted character:
118 
119         ///Line.
120         uint line_ = 0;
121         ///Column.
122         uint column_ = 0;
123         ///Whitespace character?
124         bool whitespace_ = true;
125         ///indentation space, '-', '?', or ':'?
126         bool indentation_ = true;
127 
128         ///Does the document require an explicit document indicator?
129         bool openEnded_;
130 
131         ///Formatting details.
132 
133         ///Canonical scalar format?
134         bool canonical_;
135         ///Best indentation width.
136         uint bestIndent_ = 2;
137         ///Best text width.
138         uint bestWidth_ = 80;
139         ///Best line break character/s.
140         LineBreak bestLineBreak_;
141 
142         ///Tag directive handle - prefix pairs.
143         TagDirective[] tagDirectives_;
144 
145         ///Anchor/alias to process.
146         string preparedAnchor_ = null;
147         ///Tag to process.
148         string preparedTag_ = null;
149 
150         ///Analysis result of the current scalar.
151         ScalarAnalysis analysis_;
152         ///Style of the current scalar.
153         ScalarStyle style_ = ScalarStyle.invalid;
154 
155     public:
156         @disable int opCmp(ref Emitter);
157         @disable bool opEquals(ref Emitter);
158 
159         /**
160          * Construct an emitter.
161          *
162          * Params:  stream    = Output range to write to.
163          *          canonical = Write scalars in canonical form?
164          *          indent    = Indentation width.
165          *          lineBreak = Line break character/s.
166          */
167         this(Range stream, const bool canonical, const int indent, const int width,
168              const LineBreak lineBreak) @safe
169         {
170             states_.reserve(32);
171             indents_.reserve(32);
172             stream_ = stream;
173             canonical_ = canonical;
174             nextExpected!"expectStreamStart"();
175 
176             if(indent > 1 && indent < 10){bestIndent_ = indent;}
177             if(width > bestIndent_ * 2)  {bestWidth_ = width;}
178             bestLineBreak_ = lineBreak;
179 
180             analysis_.flags.isNull = true;
181         }
182 
183         ///Emit an event.
184         void emit(Event event) @safe
185         {
186             events_.push(event);
187             while(!needMoreEvents())
188             {
189                 event_ = events_.pop();
190                 callNext();
191                 event_.destroy();
192             }
193         }
194 
195     private:
196         ///Pop and return the newest state in states_.
197         EmitterFunction popState() @safe
198             in(states_.data.length > 0,
199                 "Emitter: Need to pop a state but there are no states left")
200         {
201             const result = states_.data[$-1];
202             states_.shrinkTo(states_.data.length - 1);
203             return result;
204         }
205 
206         void pushState(string D)() @safe
207         {
208             states_ ~= mixin("function(typeof(this)* self) { self."~D~"(); }");
209         }
210 
211         ///Pop and return the newest indent in indents_.
212         int popIndent() @safe
213             in(indents_.data.length > 0,
214                 "Emitter: Need to pop an indent level but there" ~
215                 " are no indent levels left")
216         {
217             const result = indents_.data[$-1];
218             indents_.shrinkTo(indents_.data.length - 1);
219             return result;
220         }
221 
222         ///Write a string to the file/stream.
223         void writeString(const scope char[] str) @safe
224         {
225             static if(is(CharType == char))
226             {
227                 copy(str, stream_);
228             }
229             static if(is(CharType == wchar))
230             {
231                 const buffer = to!wstring(str);
232                 copy(buffer, stream_);
233             }
234             static if(is(CharType == dchar))
235             {
236                 const buffer = to!dstring(str);
237                 copy(buffer, stream_);
238             }
239         }
240 
241         ///In some cases, we wait for a few next events before emitting.
242         bool needMoreEvents() @safe nothrow
243         {
244             if(events_.length == 0){return true;}
245 
246             const event = events_.peek();
247             if(event.id == EventID.documentStart){return needEvents(1);}
248             if(event.id == EventID.sequenceStart){return needEvents(2);}
249             if(event.id == EventID.mappingStart) {return needEvents(3);}
250 
251             return false;
252         }
253 
254         ///Determines if we need specified number of more events.
255         bool needEvents(in uint count) @safe nothrow
256         {
257             int level;
258 
259             foreach(const event; events_.range)
260             {
261                 if(event.id.among!(EventID.documentStart, EventID.sequenceStart, EventID.mappingStart)) {++level;}
262                 else if(event.id.among!(EventID.documentEnd, EventID.sequenceEnd, EventID.mappingEnd)) {--level;}
263                 else if(event.id == EventID.streamStart){level = -1;}
264 
265                 if(level < 0)
266                 {
267                     return false;
268                 }
269             }
270 
271             return events_.length < (count + 1);
272         }
273 
274         ///Increase indentation level.
275         void increaseIndent(const Flag!"flow" flow = No.flow, const bool indentless = false) @safe
276         {
277             indents_ ~= indent_;
278             if(indent_ == -1)
279             {
280                 indent_ = flow ? bestIndent_ : 0;
281             }
282             else if(!indentless)
283             {
284                 indent_ += bestIndent_;
285             }
286         }
287 
288         ///Determines if the type of current event is as specified. Throws if no event.
289         bool eventTypeIs(in EventID id) const pure @safe
290             in(!event_.isNull, "Expected an event, but no event is available.")
291         {
292             return event_.id == id;
293         }
294 
295 
296         //States.
297 
298 
299         //Stream handlers.
300 
301         ///Handle start of a file/stream.
302         void expectStreamStart() @safe
303             in(eventTypeIs(EventID.streamStart),
304                 "Expected streamStart, but got " ~ event_.idString)
305         {
306 
307             writeStreamStart();
308             nextExpected!"expectDocumentStart!(Yes.first)"();
309         }
310 
311         ///Expect nothing, throwing if we still have something.
312         void expectNothing() @safe
313         {
314             assert(0, "Expected nothing, but got " ~ event_.idString);
315         }
316 
317         //Document handlers.
318 
319         ///Handle start of a document.
320         void expectDocumentStart(Flag!"first" first)() @safe
321             in(eventTypeIs(EventID.documentStart) || eventTypeIs(EventID.streamEnd),
322                 "Expected documentStart or streamEnd, but got " ~ event_.idString)
323         {
324 
325             if(event_.id == EventID.documentStart)
326             {
327                 const YAMLVersion = event_.value;
328                 auto tagDirectives = event_.tagDirectives;
329                 if(openEnded_ && (YAMLVersion !is null || tagDirectives !is null))
330                 {
331                     writeIndicator("...", Yes.needWhitespace);
332                     writeIndent();
333                 }
334 
335                 if(YAMLVersion !is null)
336                 {
337                     writeVersionDirective(prepareVersion(YAMLVersion));
338                 }
339 
340                 if(tagDirectives !is null)
341                 {
342                     tagDirectives_ = tagDirectives;
343                     sort!"icmp(a.handle, b.handle) < 0"(tagDirectives_);
344 
345                     foreach(ref pair; tagDirectives_)
346                     {
347                         writeTagDirective(prepareTagHandle(pair.handle),
348                                           prepareTagPrefix(pair.prefix));
349                     }
350                 }
351 
352                 bool eq(ref TagDirective a, ref TagDirective b){return a.handle == b.handle;}
353                 //Add any default tag directives that have not been overriden.
354                 foreach(ref def; defaultTagDirectives_)
355                 {
356                     if(!std.algorithm.canFind!eq(tagDirectives_, def))
357                     {
358                         tagDirectives_ ~= def;
359                     }
360                 }
361 
362                 const implicit = first && !event_.explicitDocument && !canonical_ &&
363                                  YAMLVersion is null && tagDirectives is null &&
364                                  !checkEmptyDocument();
365                 if(!implicit)
366                 {
367                     writeIndent();
368                     writeIndicator("---", Yes.needWhitespace);
369                     if(canonical_){writeIndent();}
370                 }
371                 nextExpected!"expectRootNode"();
372             }
373             else if(event_.id == EventID.streamEnd)
374             {
375                 if(openEnded_)
376                 {
377                     writeIndicator("...", Yes.needWhitespace);
378                     writeIndent();
379                 }
380                 writeStreamEnd();
381                 nextExpected!"expectNothing"();
382             }
383         }
384 
385         ///Handle end of a document.
386         void expectDocumentEnd() @safe
387             in(eventTypeIs(EventID.documentEnd),
388                 "Expected DocumentEnd, but got " ~ event_.idString)
389         {
390 
391             writeIndent();
392             if(event_.explicitDocument)
393             {
394                 writeIndicator("...", Yes.needWhitespace);
395                 writeIndent();
396             }
397             nextExpected!"expectDocumentStart!(No.first)"();
398         }
399 
400         ///Handle the root node of a document.
401         void expectRootNode() @safe
402         {
403             pushState!"expectDocumentEnd"();
404             expectNode(Context.root);
405         }
406 
407         ///Handle a mapping node.
408         //
409         //Params: simpleKey = Are we in a simple key?
410         void expectMappingNode(const bool simpleKey = false) @safe
411         {
412             expectNode(simpleKey ? Context.mappingSimpleKey : Context.mappingNoSimpleKey);
413         }
414 
415         ///Handle a sequence node.
416         void expectSequenceNode() @safe
417         {
418             expectNode(Context.sequence);
419         }
420 
421         ///Handle a new node. Context specifies where in the document we are.
422         void expectNode(const Context context) @safe
423         {
424             context_ = context;
425 
426             const flowCollection = event_.collectionStyle == CollectionStyle.flow;
427 
428             switch(event_.id)
429             {
430                 case EventID.alias_: expectAlias(); break;
431                 case EventID.scalar:
432                      processAnchor("&");
433                      processTag();
434                      expectScalar();
435                      break;
436                 case EventID.sequenceStart:
437                      processAnchor("&");
438                      processTag();
439                      if(flowLevel_ > 0 || canonical_ || flowCollection || checkEmptySequence())
440                      {
441                          expectFlowSequence();
442                      }
443                      else
444                      {
445                          expectBlockSequence();
446                      }
447                      break;
448                 case EventID.mappingStart:
449                      processAnchor("&");
450                      processTag();
451                      if(flowLevel_ > 0 || canonical_ || flowCollection || checkEmptyMapping())
452                      {
453                          expectFlowMapping();
454                      }
455                      else
456                      {
457                          expectBlockMapping();
458                      }
459                      break;
460                 default:
461                      assert(0, "Expected alias_, scalar, sequenceStart or " ~
462                                      "mappingStart, but got: " ~ event_.idString);
463             }
464         }
465         ///Handle an alias.
466         void expectAlias() @safe
467             in(event_.anchor != "", "Anchor is not specified for alias")
468         {
469             processAnchor("*");
470             nextExpected(popState());
471         }
472 
473         ///Handle a scalar.
474         void expectScalar() @safe
475         {
476             increaseIndent(Yes.flow);
477             processScalar();
478             indent_ = popIndent();
479             nextExpected(popState());
480         }
481 
482         //Flow sequence handlers.
483 
484         ///Handle a flow sequence.
485         void expectFlowSequence() @safe
486         {
487             writeIndicator("[", Yes.needWhitespace, Yes.whitespace);
488             ++flowLevel_;
489             increaseIndent(Yes.flow);
490             nextExpected!"expectFlowSequenceItem!(Yes.first)"();
491         }
492 
493         ///Handle a flow sequence item.
494         void expectFlowSequenceItem(Flag!"first" first)() @safe
495         {
496             if(event_.id == EventID.sequenceEnd)
497             {
498                 indent_ = popIndent();
499                 --flowLevel_;
500                 static if(!first) if(canonical_)
501                 {
502                     writeIndicator(",", No.needWhitespace);
503                     writeIndent();
504                 }
505                 writeIndicator("]", No.needWhitespace);
506                 nextExpected(popState());
507                 return;
508             }
509             static if(!first){writeIndicator(",", No.needWhitespace);}
510             if(canonical_ || column_ > bestWidth_){writeIndent();}
511             pushState!"expectFlowSequenceItem!(No.first)"();
512             expectSequenceNode();
513         }
514 
515         //Flow mapping handlers.
516 
517         ///Handle a flow mapping.
518         void expectFlowMapping() @safe
519         {
520             writeIndicator("{", Yes.needWhitespace, Yes.whitespace);
521             ++flowLevel_;
522             increaseIndent(Yes.flow);
523             nextExpected!"expectFlowMappingKey!(Yes.first)"();
524         }
525 
526         ///Handle a key in a flow mapping.
527         void expectFlowMappingKey(Flag!"first" first)() @safe
528         {
529             if(event_.id == EventID.mappingEnd)
530             {
531                 indent_ = popIndent();
532                 --flowLevel_;
533                 static if (!first) if(canonical_)
534                 {
535                     writeIndicator(",", No.needWhitespace);
536                     writeIndent();
537                 }
538                 writeIndicator("}", No.needWhitespace);
539                 nextExpected(popState());
540                 return;
541             }
542 
543             static if(!first){writeIndicator(",", No.needWhitespace);}
544             if(canonical_ || column_ > bestWidth_){writeIndent();}
545             if(!canonical_ && checkSimpleKey())
546             {
547                 pushState!"expectFlowMappingSimpleValue"();
548                 expectMappingNode(true);
549                 return;
550             }
551 
552             writeIndicator("?", Yes.needWhitespace);
553             pushState!"expectFlowMappingValue"();
554             expectMappingNode();
555         }
556 
557         ///Handle a simple value in a flow mapping.
558         void expectFlowMappingSimpleValue() @safe
559         {
560             writeIndicator(":", No.needWhitespace);
561             pushState!"expectFlowMappingKey!(No.first)"();
562             expectMappingNode();
563         }
564 
565         ///Handle a complex value in a flow mapping.
566         void expectFlowMappingValue() @safe
567         {
568             if(canonical_ || column_ > bestWidth_){writeIndent();}
569             writeIndicator(":", Yes.needWhitespace);
570             pushState!"expectFlowMappingKey!(No.first)"();
571             expectMappingNode();
572         }
573 
574         //Block sequence handlers.
575 
576         ///Handle a block sequence.
577         void expectBlockSequence() @safe
578         {
579             const indentless = (context_ == Context.mappingNoSimpleKey ||
580                                 context_ == Context.mappingSimpleKey) && !indentation_;
581             increaseIndent(No.flow, indentless);
582             nextExpected!"expectBlockSequenceItem!(Yes.first)"();
583         }
584 
585         ///Handle a block sequence item.
586         void expectBlockSequenceItem(Flag!"first" first)() @safe
587         {
588             static if(!first) if(event_.id == EventID.sequenceEnd)
589             {
590                 indent_ = popIndent();
591                 nextExpected(popState());
592                 return;
593             }
594 
595             writeIndent();
596             writeIndicator("-", Yes.needWhitespace, No.whitespace, Yes.indentation);
597             pushState!"expectBlockSequenceItem!(No.first)"();
598             expectSequenceNode();
599         }
600 
601         //Block mapping handlers.
602 
603         ///Handle a block mapping.
604         void expectBlockMapping() @safe
605         {
606             increaseIndent(No.flow);
607             nextExpected!"expectBlockMappingKey!(Yes.first)"();
608         }
609 
610         ///Handle a key in a block mapping.
611         void expectBlockMappingKey(Flag!"first" first)() @safe
612         {
613             static if(!first) if(event_.id == EventID.mappingEnd)
614             {
615                 indent_ = popIndent();
616                 nextExpected(popState());
617                 return;
618             }
619 
620             writeIndent();
621             if(checkSimpleKey())
622             {
623                 pushState!"expectBlockMappingSimpleValue"();
624                 expectMappingNode(true);
625                 return;
626             }
627 
628             writeIndicator("?", Yes.needWhitespace, No.whitespace, Yes.indentation);
629             pushState!"expectBlockMappingValue"();
630             expectMappingNode();
631         }
632 
633         ///Handle a simple value in a block mapping.
634         void expectBlockMappingSimpleValue() @safe
635         {
636             writeIndicator(":", No.needWhitespace);
637             pushState!"expectBlockMappingKey!(No.first)"();
638             expectMappingNode();
639         }
640 
641         ///Handle a complex value in a block mapping.
642         void expectBlockMappingValue() @safe
643         {
644             writeIndent();
645             writeIndicator(":", Yes.needWhitespace, No.whitespace, Yes.indentation);
646             pushState!"expectBlockMappingKey!(No.first)"();
647             expectMappingNode();
648         }
649 
650         //Checkers.
651 
652         ///Check if an empty sequence is next.
653         bool checkEmptySequence() const @safe pure nothrow
654         {
655             return event_.id == EventID.sequenceStart && events_.length > 0
656                    && events_.peek().id == EventID.sequenceEnd;
657         }
658 
659         ///Check if an empty mapping is next.
660         bool checkEmptyMapping() const @safe pure nothrow
661         {
662             return event_.id == EventID.mappingStart && events_.length > 0
663                    && events_.peek().id == EventID.mappingEnd;
664         }
665 
666         ///Check if an empty document is next.
667         bool checkEmptyDocument() const @safe pure nothrow
668         {
669             if(event_.id != EventID.documentStart || events_.length == 0)
670             {
671                 return false;
672             }
673 
674             const event = events_.peek();
675             const emptyScalar = event.id == EventID.scalar && (event.anchor is null) &&
676                                 (event.tag is null) && event.implicit && event.value == "";
677             return emptyScalar;
678         }
679 
680         ///Check if a simple key is next.
681         bool checkSimpleKey() @safe
682         {
683             uint length;
684             const id = event_.id;
685             const scalar = id == EventID.scalar;
686             const collectionStart = id == EventID.mappingStart ||
687                                     id == EventID.sequenceStart;
688 
689             if((id == EventID.alias_ || scalar || collectionStart)
690                && (event_.anchor !is null))
691             {
692                 if(preparedAnchor_ is null)
693                 {
694                     preparedAnchor_ = prepareAnchor(event_.anchor);
695                 }
696                 length += preparedAnchor_.length;
697             }
698 
699             if((scalar || collectionStart) && (event_.tag !is null))
700             {
701                 if(preparedTag_ is null){preparedTag_ = prepareTag(event_.tag);}
702                 length += preparedTag_.length;
703             }
704 
705             if(scalar)
706             {
707                 if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
708                 length += analysis_.scalar.length;
709             }
710 
711             if(length >= 128){return false;}
712 
713             return id == EventID.alias_ ||
714                    (scalar && !analysis_.flags.empty && !analysis_.flags.multiline) ||
715                    checkEmptySequence() ||
716                    checkEmptyMapping();
717         }
718 
719         ///Process and write a scalar.
720         void processScalar() @safe
721         {
722             if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
723             if(style_ == ScalarStyle.invalid)
724             {
725                 style_ = chooseScalarStyle();
726             }
727 
728             //if(analysis_.flags.multiline && (context_ != Context.mappingSimpleKey) &&
729             //   ([ScalarStyle.invalid, ScalarStyle.plain, ScalarStyle.singleQuoted, ScalarStyle.doubleQuoted)
730             //    .canFind(style_))
731             //{
732             //    writeIndent();
733             //}
734             auto writer = ScalarWriter!(Range, CharType)(&this, analysis_.scalar,
735                                        context_ != Context.mappingSimpleKey);
736             final switch(style_)
737             {
738                 case ScalarStyle.invalid:      assert(false);
739                 case ScalarStyle.doubleQuoted: writer.writeDoubleQuoted(); break;
740                 case ScalarStyle.singleQuoted: writer.writeSingleQuoted(); break;
741                 case ScalarStyle.folded:       writer.writeFolded();       break;
742                 case ScalarStyle.literal:      writer.writeLiteral();      break;
743                 case ScalarStyle.plain:        writer.writePlain();        break;
744             }
745             analysis_.flags.isNull = true;
746             style_ = ScalarStyle.invalid;
747         }
748 
749         ///Process and write an anchor/alias.
750         void processAnchor(const string indicator) @safe
751         {
752             if(event_.anchor is null)
753             {
754                 preparedAnchor_ = null;
755                 return;
756             }
757             if(preparedAnchor_ is null)
758             {
759                 preparedAnchor_ = prepareAnchor(event_.anchor);
760             }
761             if(preparedAnchor_ !is null && preparedAnchor_ != "")
762             {
763                 writeIndicator(indicator, Yes.needWhitespace);
764                 writeString(preparedAnchor_);
765             }
766             preparedAnchor_ = null;
767         }
768 
769         ///Process and write a tag.
770         void processTag() @safe
771         {
772             string tag = event_.tag;
773 
774             if(event_.id == EventID.scalar)
775             {
776                 if(style_ == ScalarStyle.invalid){style_ = chooseScalarStyle();}
777                 if((!canonical_ || (tag is null)) &&
778                    ((tag == "tag:yaml.org,2002:str") || (style_ == ScalarStyle.plain ? event_.implicit : !event_.implicit && (tag is null))))
779                 {
780                     preparedTag_ = null;
781                     return;
782                 }
783                 if(event_.implicit && (tag is null))
784                 {
785                     tag = "!";
786                     preparedTag_ = null;
787                 }
788             }
789             else if((!canonical_ || (tag is null)) && event_.implicit)
790             {
791                 preparedTag_ = null;
792                 return;
793             }
794 
795             assert(tag != "", "Tag is not specified");
796             if(preparedTag_ is null){preparedTag_ = prepareTag(tag);}
797             if(preparedTag_ !is null && preparedTag_ != "")
798             {
799                 writeIndicator(preparedTag_, Yes.needWhitespace);
800             }
801             preparedTag_ = null;
802         }
803 
804         ///Determine style to write the current scalar in.
805         ScalarStyle chooseScalarStyle() @safe
806         {
807             if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
808 
809             const style          = event_.scalarStyle;
810             const invalidOrPlain = style == ScalarStyle.invalid || style == ScalarStyle.plain;
811             const block          = style == ScalarStyle.literal || style == ScalarStyle.folded;
812             const singleQuoted   = style == ScalarStyle.singleQuoted;
813             const doubleQuoted   = style == ScalarStyle.doubleQuoted;
814 
815             const allowPlain     = flowLevel_ > 0 ? analysis_.flags.allowFlowPlain
816                                                   : analysis_.flags.allowBlockPlain;
817             //simple empty or multiline scalars can't be written in plain style
818             const simpleNonPlain = (context_ == Context.mappingSimpleKey) &&
819                                    (analysis_.flags.empty || analysis_.flags.multiline);
820 
821             if(doubleQuoted || canonical_)
822             {
823                 return ScalarStyle.doubleQuoted;
824             }
825 
826             if(invalidOrPlain && event_.implicit && !simpleNonPlain && allowPlain)
827             {
828                 return ScalarStyle.plain;
829             }
830 
831             if(block && flowLevel_ == 0 && context_ != Context.mappingSimpleKey &&
832                analysis_.flags.allowBlock)
833             {
834                 return style;
835             }
836 
837             if((invalidOrPlain || singleQuoted) &&
838                analysis_.flags.allowSingleQuoted &&
839                !(context_ == Context.mappingSimpleKey && analysis_.flags.multiline))
840             {
841                 return ScalarStyle.singleQuoted;
842             }
843 
844             return ScalarStyle.doubleQuoted;
845         }
846 
847         ///Prepare YAML version string for output.
848         static string prepareVersion(const string YAMLVersion) @safe
849             in(YAMLVersion.split(".")[0] == "1",
850                 "Unsupported YAML version: " ~ YAMLVersion)
851         {
852             return YAMLVersion;
853         }
854 
855         ///Encode an Unicode character for tag directive and write it to writer.
856         static void encodeChar(Writer)(ref Writer writer, in dchar c) @safe
857         {
858             char[4] data;
859             const bytes = encode(data, c);
860             //For each byte add string in format %AB , where AB are hex digits of the byte.
861             foreach(const char b; data[0 .. bytes])
862             {
863                 formattedWrite(writer, "%%%02X", cast(ubyte)b);
864             }
865         }
866 
867         ///Prepare tag directive handle for output.
868         static string prepareTagHandle(const string handle) @safe
869             in(handle != "", "Tag handle must not be empty")
870             in(handle.drop(1).dropBack(1).all!(c => isAlphaNum(c) || c.among!('-', '_')),
871                 "Tag handle contains invalid characters")
872         {
873             return handle;
874         }
875 
876         ///Prepare tag directive prefix for output.
877         static string prepareTagPrefix(const string prefix) @safe
878             in(prefix != "", "Tag prefix must not be empty")
879         {
880             auto appender = appender!string();
881             const int offset = prefix[0] == '!';
882             size_t start, end;
883 
884             foreach(const size_t i, const dchar c; prefix)
885             {
886                 const size_t idx = i + offset;
887                 if(isAlphaNum(c) || c.among!('-', ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '!', '~', '*', '\\', '\'', '(', ')', '[', ']', '%'))
888                 {
889                     end = idx + 1;
890                     continue;
891                 }
892 
893                 if(start < idx){appender.put(prefix[start .. idx]);}
894                 start = end = idx + 1;
895 
896                 encodeChar(appender, c);
897             }
898 
899             end = min(end, prefix.length);
900             if(start < end){appender.put(prefix[start .. end]);}
901             return appender.data;
902         }
903 
904         ///Prepare tag for output.
905         string prepareTag(in string tag) @safe
906             in(tag != "", "Tag must not be empty")
907         {
908 
909             string tagString = tag;
910             if (tagString == "!") return "!";
911             string handle;
912             string suffix = tagString;
913 
914             //Sort lexicographically by prefix.
915             sort!"icmp(a.prefix, b.prefix) < 0"(tagDirectives_);
916             foreach(ref pair; tagDirectives_)
917             {
918                 auto prefix = pair.prefix;
919                 if(tagString.startsWith(prefix) &&
920                    (prefix != "!" || prefix.length < tagString.length))
921                 {
922                     handle = pair.handle;
923                     suffix = tagString[prefix.length .. $];
924                 }
925             }
926 
927             auto appender = appender!string();
928             appender.put(handle !is null && handle != "" ? handle : "!<");
929             size_t start, end;
930             foreach(const dchar c; suffix)
931             {
932                 if(isAlphaNum(c) || c.among!('-', ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\\', '\'', '(', ')', '[', ']') ||
933                    (c == '!' && handle != "!"))
934                 {
935                     ++end;
936                     continue;
937                 }
938                 if(start < end){appender.put(suffix[start .. end]);}
939                 start = end = end + 1;
940 
941                 encodeChar(appender, c);
942             }
943 
944             if(start < end){appender.put(suffix[start .. end]);}
945             if(handle is null || handle == ""){appender.put(">");}
946 
947             return appender.data;
948         }
949 
950         ///Prepare anchor for output.
951         static string prepareAnchor(const string anchor) @safe
952             in(anchor != "",  "Anchor must not be empty")
953             in(anchor.all!isNSAnchorName, "Anchor contains invalid characters")
954         {
955             return anchor;
956         }
957 
958         ///Analyze specifed scalar and return the analysis result.
959         static ScalarAnalysis analyzeScalar(string scalar) @safe
960         {
961             ScalarAnalysis analysis;
962             analysis.flags.isNull = false;
963             analysis.scalar = scalar;
964 
965             //Empty scalar is a special case.
966             if(scalar is null || scalar == "")
967             {
968                 with(ScalarAnalysis.AnalysisFlags)
969                     analysis.flags =
970                         empty |
971                         allowBlockPlain |
972                         allowSingleQuoted |
973                         allowDoubleQuoted;
974                 return analysis;
975             }
976 
977             //Indicators and special characters (All false by default).
978             bool blockIndicators, flowIndicators, lineBreaks, specialCharacters;
979 
980             //Important whitespace combinations (All false by default).
981             bool leadingSpace, leadingBreak, trailingSpace, trailingBreak,
982                  breakSpace, spaceBreak;
983 
984             //Check document indicators.
985             if(scalar.startsWith("---", "..."))
986             {
987                 blockIndicators = flowIndicators = true;
988             }
989 
990             //First character or preceded by a whitespace.
991             bool preceededByWhitespace = true;
992 
993             //Last character or followed by a whitespace.
994             bool followedByWhitespace = scalar.length == 1 ||
995                                         scalar[1].among!(' ', '\t', '\0', '\n', '\r', '\u0085', '\u2028', '\u2029');
996 
997             //The previous character is a space/break (false by default).
998             bool previousSpace, previousBreak;
999 
1000             foreach(const size_t index, const dchar c; scalar)
1001             {
1002                 //Check for indicators.
1003                 if(index == 0)
1004                 {
1005                     //Leading indicators are special characters.
1006                     if(c.isSpecialChar)
1007                     {
1008                         flowIndicators = blockIndicators = true;
1009                     }
1010                     if(':' == c || '?' == c)
1011                     {
1012                         flowIndicators = true;
1013                         if(followedByWhitespace){blockIndicators = true;}
1014                     }
1015                     if(c == '-' && followedByWhitespace)
1016                     {
1017                         flowIndicators = blockIndicators = true;
1018                     }
1019                 }
1020                 else
1021                 {
1022                     //Some indicators cannot appear within a scalar as well.
1023                     if(c.isFlowIndicator){flowIndicators = true;}
1024                     if(c == ':')
1025                     {
1026                         flowIndicators = true;
1027                         if(followedByWhitespace){blockIndicators = true;}
1028                     }
1029                     if(c == '#' && preceededByWhitespace)
1030                     {
1031                         flowIndicators = blockIndicators = true;
1032                     }
1033                 }
1034 
1035                 //Check for line breaks, special, and unicode characters.
1036                 if(c.isNewLine){lineBreaks = true;}
1037                 if(!(c == '\n' || (c >= '\x20' && c <= '\x7E')) &&
1038                    !((c == '\u0085' || (c >= '\xA0' && c <= '\uD7FF') ||
1039                      (c >= '\uE000' && c <= '\uFFFD')) && c != '\uFEFF'))
1040                 {
1041                     specialCharacters = true;
1042                 }
1043 
1044                 //Detect important whitespace combinations.
1045                 if(c == ' ')
1046                 {
1047                     if(index == 0){leadingSpace = true;}
1048                     if(index == scalar.length - 1){trailingSpace = true;}
1049                     if(previousBreak){breakSpace = true;}
1050                     previousSpace = true;
1051                     previousBreak = false;
1052                 }
1053                 else if(c.isNewLine)
1054                 {
1055                     if(index == 0){leadingBreak = true;}
1056                     if(index == scalar.length - 1){trailingBreak = true;}
1057                     if(previousSpace){spaceBreak = true;}
1058                     previousSpace = false;
1059                     previousBreak = true;
1060                 }
1061                 else
1062                 {
1063                     previousSpace = previousBreak = false;
1064                 }
1065 
1066                 //Prepare for the next character.
1067                 preceededByWhitespace = c.isSpace != 0;
1068                 followedByWhitespace = index + 2 >= scalar.length ||
1069                                        scalar[index + 2].isSpace;
1070             }
1071 
1072             with(ScalarAnalysis.AnalysisFlags)
1073             {
1074                 //Let's decide what styles are allowed.
1075                 analysis.flags |= allowFlowPlain | allowBlockPlain | allowSingleQuoted |
1076                                allowDoubleQuoted | allowBlock;
1077 
1078                 //Leading and trailing whitespaces are bad for plain scalars.
1079                 if(leadingSpace || leadingBreak || trailingSpace || trailingBreak)
1080                 {
1081                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain);
1082                 }
1083 
1084                 //We do not permit trailing spaces for block scalars.
1085                 if(trailingSpace)
1086                 {
1087                     analysis.flags &= ~allowBlock;
1088                 }
1089 
1090                 //Spaces at the beginning of a new line are only acceptable for block
1091                 //scalars.
1092                 if(breakSpace)
1093                 {
1094                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain | allowSingleQuoted);
1095                 }
1096 
1097                 //Spaces followed by breaks, as well as special character are only
1098                 //allowed for double quoted scalars.
1099                 if(spaceBreak || specialCharacters)
1100                 {
1101                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain | allowSingleQuoted | allowBlock);
1102                 }
1103 
1104                 //Although the plain scalar writer supports breaks, we never emit
1105                 //multiline plain scalars.
1106                 if(lineBreaks)
1107                 {
1108                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain);
1109                     analysis.flags |= multiline;
1110                 }
1111 
1112                 //Flow indicators are forbidden for flow plain scalars.
1113                 if(flowIndicators)
1114                 {
1115                     analysis.flags &= ~allowFlowPlain;
1116                 }
1117 
1118                 //Block indicators are forbidden for block plain scalars.
1119                 if(blockIndicators)
1120                 {
1121                     analysis.flags &= ~allowBlockPlain;
1122                 }
1123             }
1124             return analysis;
1125         }
1126 
1127         @safe unittest
1128         {
1129             with(analyzeScalar("").flags)
1130             {
1131                 // workaround for empty being std.range.primitives.empty here
1132                 alias empty = ScalarAnalysis.AnalysisFlags.empty;
1133                 assert(empty && allowBlockPlain && allowSingleQuoted && allowDoubleQuoted);
1134             }
1135             with(analyzeScalar("a").flags)
1136             {
1137                 assert(allowFlowPlain && allowBlockPlain && allowSingleQuoted && allowDoubleQuoted && allowBlock);
1138             }
1139             with(analyzeScalar(" ").flags)
1140             {
1141                 assert(allowSingleQuoted && allowDoubleQuoted);
1142             }
1143             with(analyzeScalar(" a").flags)
1144             {
1145                 assert(allowSingleQuoted && allowDoubleQuoted);
1146             }
1147             with(analyzeScalar("a ").flags)
1148             {
1149                 assert(allowSingleQuoted && allowDoubleQuoted);
1150             }
1151             with(analyzeScalar("\na").flags)
1152             {
1153                 assert(allowSingleQuoted && allowDoubleQuoted);
1154             }
1155             with(analyzeScalar("a\n").flags)
1156             {
1157                 assert(allowSingleQuoted && allowDoubleQuoted);
1158             }
1159             with(analyzeScalar("\n").flags)
1160             {
1161                 assert(multiline && allowSingleQuoted && allowDoubleQuoted && allowBlock);
1162             }
1163             with(analyzeScalar(" \n").flags)
1164             {
1165                 assert(multiline && allowDoubleQuoted);
1166             }
1167             with(analyzeScalar("\n a").flags)
1168             {
1169                 assert(multiline && allowDoubleQuoted && allowBlock);
1170             }
1171         }
1172 
1173         //Writers.
1174 
1175         ///Start the YAML stream (write the unicode byte order mark).
1176         void writeStreamStart() @safe
1177         {
1178             //Write BOM (except for UTF-8)
1179             static if(is(CharType == wchar) || is(CharType == dchar))
1180             {
1181                 stream_.put(cast(CharType)'\uFEFF');
1182             }
1183         }
1184 
1185         ///End the YAML stream.
1186         void writeStreamEnd() @safe {}
1187 
1188         ///Write an indicator (e.g. ":", "[", ">", etc.).
1189         void writeIndicator(const scope char[] indicator,
1190                             const Flag!"needWhitespace" needWhitespace,
1191                             const Flag!"whitespace" whitespace = No.whitespace,
1192                             const Flag!"indentation" indentation = No.indentation) @safe
1193         {
1194             const bool prefixSpace = !whitespace_ && needWhitespace;
1195             whitespace_  = whitespace;
1196             indentation_ = indentation_ && indentation;
1197             openEnded_   = false;
1198             column_ += indicator.length;
1199             if(prefixSpace)
1200             {
1201                 ++column_;
1202                 writeString(" ");
1203             }
1204             writeString(indicator);
1205         }
1206 
1207         ///Write indentation.
1208         void writeIndent() @safe
1209         {
1210             const indent = indent_ == -1 ? 0 : indent_;
1211 
1212             if(!indentation_ || column_ > indent || (column_ == indent && !whitespace_))
1213             {
1214                 writeLineBreak();
1215             }
1216             if(column_ < indent)
1217             {
1218                 whitespace_ = true;
1219 
1220                 //Used to avoid allocation of arbitrary length strings.
1221                 static immutable spaces = "    ";
1222                 size_t numSpaces = indent - column_;
1223                 column_ = indent;
1224                 while(numSpaces >= spaces.length)
1225                 {
1226                     writeString(spaces);
1227                     numSpaces -= spaces.length;
1228                 }
1229                 writeString(spaces[0 .. numSpaces]);
1230             }
1231         }
1232 
1233         ///Start new line.
1234         void writeLineBreak(const scope char[] data = null) @safe
1235         {
1236             whitespace_ = indentation_ = true;
1237             ++line_;
1238             column_ = 0;
1239             writeString(data is null ? lineBreak(bestLineBreak_) : data);
1240         }
1241 
1242         ///Write a YAML version directive.
1243         void writeVersionDirective(const string versionText) @safe
1244         {
1245             writeString("%YAML ");
1246             writeString(versionText);
1247             writeLineBreak();
1248         }
1249 
1250         ///Write a tag directive.
1251         void writeTagDirective(const string handle, const string prefix) @safe
1252         {
1253             writeString("%TAG ");
1254             writeString(handle);
1255             writeString(" ");
1256             writeString(prefix);
1257             writeLineBreak();
1258         }
1259         void nextExpected(string D)() @safe
1260         {
1261             state_ = mixin("function(typeof(this)* self) { self."~D~"(); }");
1262         }
1263         void nextExpected(EmitterFunction f) @safe
1264         {
1265             state_ = f;
1266         }
1267         void callNext() @safe
1268         {
1269             state_(&this);
1270         }
1271 }
1272 
1273 
1274 private:
1275 
1276 ///RAII struct used to write out scalar values.
1277 struct ScalarWriter(Range, CharType)
1278 {
1279     invariant()
1280     {
1281         assert(emitter_.bestIndent_ > 0 && emitter_.bestIndent_ < 10,
1282                "Emitter bestIndent must be 1 to 9 for one-character indent hint");
1283     }
1284 
1285     private:
1286         @disable int opCmp(ref Emitter!(Range, CharType));
1287         @disable bool opEquals(ref Emitter!(Range, CharType));
1288 
1289         ///Used as "null" UTF-32 character.
1290         static immutable dcharNone = dchar.max;
1291 
1292         ///Emitter used to emit the scalar.
1293         Emitter!(Range, CharType)* emitter_;
1294 
1295         ///UTF-8 encoded text of the scalar to write.
1296         string text_;
1297 
1298         ///Can we split the scalar into multiple lines?
1299         bool split_;
1300         ///Are we currently going over spaces in the text?
1301         bool spaces_;
1302         ///Are we currently going over line breaks in the text?
1303         bool breaks_;
1304 
1305         ///Start and end byte of the text range we're currently working with.
1306         size_t startByte_, endByte_;
1307         ///End byte of the text range including the currently processed character.
1308         size_t nextEndByte_;
1309         ///Start and end character of the text range we're currently working with.
1310         long startChar_, endChar_;
1311 
1312     public:
1313         ///Construct a ScalarWriter using emitter to output text.
1314         this(Emitter!(Range, CharType)* emitter, string text, const bool split = true) @safe nothrow
1315         {
1316             emitter_ = emitter;
1317             text_ = text;
1318             split_ = split;
1319         }
1320 
1321         ///Write text as single quoted scalar.
1322         void writeSingleQuoted() @safe
1323         {
1324             emitter_.writeIndicator("\'", Yes.needWhitespace);
1325             spaces_ = breaks_ = false;
1326             resetTextPosition();
1327 
1328             do
1329             {
1330                 const dchar c = nextChar();
1331                 if(spaces_)
1332                 {
1333                     if(c != ' ' && tooWide() && split_ &&
1334                        startByte_ != 0 && endByte_ != text_.length)
1335                     {
1336                         writeIndent(Flag!"ResetSpace".no);
1337                         updateRangeStart();
1338                     }
1339                     else if(c != ' ')
1340                     {
1341                         writeCurrentRange(Flag!"UpdateColumn".yes);
1342                     }
1343                 }
1344                 else if(breaks_)
1345                 {
1346                     if(!c.isNewLine)
1347                     {
1348                         writeStartLineBreak();
1349                         writeLineBreaks();
1350                         emitter_.writeIndent();
1351                     }
1352                 }
1353                 else if((c == dcharNone || c == '\'' || c == ' ' || c.isNewLine)
1354                         && startChar_ < endChar_)
1355                 {
1356                     writeCurrentRange(Flag!"UpdateColumn".yes);
1357                 }
1358                 if(c == '\'')
1359                 {
1360                     emitter_.column_ += 2;
1361                     emitter_.writeString("\'\'");
1362                     startByte_ = endByte_ + 1;
1363                     startChar_ = endChar_ + 1;
1364                 }
1365                 updateBreaks(c, Flag!"UpdateSpaces".yes);
1366             }while(endByte_ < text_.length);
1367 
1368             emitter_.writeIndicator("\'", No.needWhitespace);
1369         }
1370 
1371         ///Write text as double quoted scalar.
1372         void writeDoubleQuoted() @safe
1373         {
1374             resetTextPosition();
1375             emitter_.writeIndicator("\"", Yes.needWhitespace);
1376             do
1377             {
1378                 const dchar c = nextChar();
1379                 //handle special characters
1380                 if(c == dcharNone || c.among!('\"', '\\', '\u0085', '\u2028', '\u2029', '\uFEFF') ||
1381                    !((c >= '\x20' && c <= '\x7E') ||
1382                      ((c >= '\xA0' && c <= '\uD7FF') || (c >= '\uE000' && c <= '\uFFFD'))))
1383                 {
1384                     if(startChar_ < endChar_)
1385                     {
1386                         writeCurrentRange(Flag!"UpdateColumn".yes);
1387                     }
1388                     if(c != dcharNone)
1389                     {
1390                         auto appender = appender!string();
1391                         if(const dchar es = toEscape(c))
1392                         {
1393                             appender.put('\\');
1394                             appender.put(es);
1395                         }
1396                         else
1397                         {
1398                             //Write an escaped Unicode character.
1399                             const format = c <= 255   ? "\\x%02X":
1400                                            c <= 65535 ? "\\u%04X": "\\U%08X";
1401                             formattedWrite(appender, format, cast(uint)c);
1402                         }
1403 
1404                         emitter_.column_ += appender.data.length;
1405                         emitter_.writeString(appender.data);
1406                         startChar_ = endChar_ + 1;
1407                         startByte_ = nextEndByte_;
1408                     }
1409                 }
1410                 if((endByte_ > 0 && endByte_ < text_.length - strideBack(text_, text_.length))
1411                    && (c == ' ' || startChar_ >= endChar_)
1412                    && (emitter_.column_ + endChar_ - startChar_ > emitter_.bestWidth_)
1413                    && split_)
1414                 {
1415                     //text_[2:1] is ok in Python but not in D, so we have to use min()
1416                     emitter_.writeString(text_[min(startByte_, endByte_) .. endByte_]);
1417                     emitter_.writeString("\\");
1418                     emitter_.column_ += startChar_ - endChar_ + 1;
1419                     startChar_ = max(startChar_, endChar_);
1420                     startByte_ = max(startByte_, endByte_);
1421 
1422                     writeIndent(Flag!"ResetSpace".yes);
1423                     if(charAtStart() == ' ')
1424                     {
1425                         emitter_.writeString("\\");
1426                         ++emitter_.column_;
1427                     }
1428                 }
1429             }while(endByte_ < text_.length);
1430             emitter_.writeIndicator("\"", No.needWhitespace);
1431         }
1432 
1433         ///Write text as folded block scalar.
1434         void writeFolded() @safe
1435         {
1436             initBlock('>');
1437             bool leadingSpace = true;
1438             spaces_ = false;
1439             breaks_ = true;
1440             resetTextPosition();
1441 
1442             do
1443             {
1444                 const dchar c = nextChar();
1445                 if(breaks_)
1446                 {
1447                     if(!c.isNewLine)
1448                     {
1449                         if(!leadingSpace && c != dcharNone && c != ' ')
1450                         {
1451                             writeStartLineBreak();
1452                         }
1453                         leadingSpace = (c == ' ');
1454                         writeLineBreaks();
1455                         if(c != dcharNone){emitter_.writeIndent();}
1456                     }
1457                 }
1458                 else if(spaces_)
1459                 {
1460                     if(c != ' ' && tooWide())
1461                     {
1462                         writeIndent(Flag!"ResetSpace".no);
1463                         updateRangeStart();
1464                     }
1465                     else if(c != ' ')
1466                     {
1467                         writeCurrentRange(Flag!"UpdateColumn".yes);
1468                     }
1469                 }
1470                 else if(c == dcharNone || c.isNewLine || c == ' ')
1471                 {
1472                     writeCurrentRange(Flag!"UpdateColumn".yes);
1473                     if(c == dcharNone){emitter_.writeLineBreak();}
1474                 }
1475                 updateBreaks(c, Flag!"UpdateSpaces".yes);
1476             }while(endByte_ < text_.length);
1477         }
1478 
1479         ///Write text as literal block scalar.
1480         void writeLiteral() @safe
1481         {
1482             initBlock('|');
1483             breaks_ = true;
1484             resetTextPosition();
1485 
1486             do
1487             {
1488                 const dchar c = nextChar();
1489                 if(breaks_)
1490                 {
1491                     if(!c.isNewLine)
1492                     {
1493                         writeLineBreaks();
1494                         if(c != dcharNone){emitter_.writeIndent();}
1495                     }
1496                 }
1497                 else if(c == dcharNone || c.isNewLine)
1498                 {
1499                     writeCurrentRange(Flag!"UpdateColumn".no);
1500                     if(c == dcharNone){emitter_.writeLineBreak();}
1501                 }
1502                 updateBreaks(c, Flag!"UpdateSpaces".no);
1503             }while(endByte_ < text_.length);
1504         }
1505 
1506         ///Write text as plain scalar.
1507         void writePlain() @safe
1508         {
1509             if(emitter_.context_ == Emitter!(Range, CharType).Context.root){emitter_.openEnded_ = true;}
1510             if(text_ == ""){return;}
1511             if(!emitter_.whitespace_)
1512             {
1513                 ++emitter_.column_;
1514                 emitter_.writeString(" ");
1515             }
1516             emitter_.whitespace_ = emitter_.indentation_ = false;
1517             spaces_ = breaks_ = false;
1518             resetTextPosition();
1519 
1520             do
1521             {
1522                 const dchar c = nextChar();
1523                 if(spaces_)
1524                 {
1525                     if(c != ' ' && tooWide() && split_)
1526                     {
1527                         writeIndent(Flag!"ResetSpace".yes);
1528                         updateRangeStart();
1529                     }
1530                     else if(c != ' ')
1531                     {
1532                         writeCurrentRange(Flag!"UpdateColumn".yes);
1533                     }
1534                 }
1535                 else if(breaks_)
1536                 {
1537                     if(!c.isNewLine)
1538                     {
1539                         writeStartLineBreak();
1540                         writeLineBreaks();
1541                         writeIndent(Flag!"ResetSpace".yes);
1542                     }
1543                 }
1544                 else if(c == dcharNone || c.isNewLine || c == ' ')
1545                 {
1546                     writeCurrentRange(Flag!"UpdateColumn".yes);
1547                 }
1548                 updateBreaks(c, Flag!"UpdateSpaces".yes);
1549             }while(endByte_ < text_.length);
1550         }
1551 
1552     private:
1553         ///Get next character and move end of the text range to it.
1554         @property dchar nextChar() pure @safe
1555         {
1556             ++endChar_;
1557             endByte_ = nextEndByte_;
1558             if(endByte_ >= text_.length){return dcharNone;}
1559             const c = text_[nextEndByte_];
1560             //c is ascii, no need to decode.
1561             if(c < 0x80)
1562             {
1563                 ++nextEndByte_;
1564                 return c;
1565             }
1566             return decode(text_, nextEndByte_);
1567         }
1568 
1569         ///Get character at start of the text range.
1570         @property dchar charAtStart() const pure @safe
1571         {
1572             size_t idx = startByte_;
1573             return decode(text_, idx);
1574         }
1575 
1576         ///Is the current line too wide?
1577         @property bool tooWide() const pure @safe nothrow
1578         {
1579             return startChar_ + 1 == endChar_ &&
1580                    emitter_.column_ > emitter_.bestWidth_;
1581         }
1582 
1583         ///Determine hints (indicators) for block scalar.
1584         size_t determineBlockHints(char[] hints, uint bestIndent) const pure @safe
1585         {
1586             size_t hintsIdx;
1587             if(text_.length == 0)
1588                 return hintsIdx;
1589 
1590             dchar lastChar(const string str, ref size_t end)
1591             {
1592                 size_t idx = end = end - strideBack(str, end);
1593                 return decode(text_, idx);
1594             }
1595 
1596             size_t end = text_.length;
1597             const last = lastChar(text_, end);
1598             const secondLast = end > 0 ? lastChar(text_, end) : 0;
1599 
1600             if(text_[0].isNewLine || text_[0] == ' ')
1601             {
1602                 hints[hintsIdx++] = cast(char)('0' + bestIndent);
1603             }
1604             if(!last.isNewLine)
1605             {
1606                 hints[hintsIdx++] = '-';
1607             }
1608             else if(std.utf.count(text_) == 1 || secondLast.isNewLine)
1609             {
1610                 hints[hintsIdx++] = '+';
1611             }
1612             return hintsIdx;
1613         }
1614 
1615         ///Initialize for block scalar writing with specified indicator.
1616         void initBlock(const char indicator) @safe
1617         {
1618             char[4] hints;
1619             hints[0] = indicator;
1620             const hintsLength = 1 + determineBlockHints(hints[1 .. $], emitter_.bestIndent_);
1621             emitter_.writeIndicator(hints[0 .. hintsLength], Yes.needWhitespace);
1622             if(hints.length > 0 && hints[$ - 1] == '+')
1623             {
1624                 emitter_.openEnded_ = true;
1625             }
1626             emitter_.writeLineBreak();
1627         }
1628 
1629         ///Write out the current text range.
1630         void writeCurrentRange(const Flag!"UpdateColumn" updateColumn) @safe
1631         {
1632             emitter_.writeString(text_[startByte_ .. endByte_]);
1633             if(updateColumn){emitter_.column_ += endChar_ - startChar_;}
1634             updateRangeStart();
1635         }
1636 
1637         ///Write line breaks in the text range.
1638         void writeLineBreaks() @safe
1639         {
1640             foreach(const dchar br; text_[startByte_ .. endByte_])
1641             {
1642                 if(br == '\n'){emitter_.writeLineBreak();}
1643                 else
1644                 {
1645                     char[4] brString;
1646                     const bytes = encode(brString, br);
1647                     emitter_.writeLineBreak(brString[0 .. bytes]);
1648                 }
1649             }
1650             updateRangeStart();
1651         }
1652 
1653         ///Write line break if start of the text range is a newline.
1654         void writeStartLineBreak() @safe
1655         {
1656             if(charAtStart == '\n'){emitter_.writeLineBreak();}
1657         }
1658 
1659         ///Write indentation, optionally resetting whitespace/indentation flags.
1660         void writeIndent(const Flag!"ResetSpace" resetSpace) @safe
1661         {
1662             emitter_.writeIndent();
1663             if(resetSpace)
1664             {
1665                 emitter_.whitespace_ = emitter_.indentation_ = false;
1666             }
1667         }
1668 
1669         ///Move start of text range to its end.
1670         void updateRangeStart() pure @safe nothrow
1671         {
1672             startByte_ = endByte_;
1673             startChar_ = endChar_;
1674         }
1675 
1676         ///Update the line breaks_ flag, optionally updating the spaces_ flag.
1677         void updateBreaks(in dchar c, const Flag!"UpdateSpaces" updateSpaces) pure @safe
1678         {
1679             if(c == dcharNone){return;}
1680             breaks_ = (c.isNewLine != 0);
1681             if(updateSpaces){spaces_ = c == ' ';}
1682         }
1683 
1684         ///Move to the beginning of text.
1685         void resetTextPosition() pure @safe nothrow
1686         {
1687             startByte_ = endByte_ = nextEndByte_ = 0;
1688             startChar_ = endChar_ = -1;
1689         }
1690 }