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