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