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