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 }