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