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