2011-10-11 13:58:23 +00:00
|
|
|
// Copyright Ferdinand Majerech 2011.
|
|
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
|
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
|
|
|
// http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* YAML emitter.
|
|
|
|
* Code based on PyYAML: http://www.pyyaml.org
|
|
|
|
*/
|
|
|
|
module dyaml.emitter;
|
|
|
|
|
|
|
|
|
|
|
|
import std.algorithm;
|
|
|
|
import std.array;
|
|
|
|
import std.ascii;
|
2011-10-23 22:46:35 +00:00
|
|
|
import std.container;
|
2011-10-11 13:58:23 +00:00
|
|
|
import std.conv;
|
|
|
|
import std.exception;
|
|
|
|
import std.format;
|
|
|
|
import std.range;
|
|
|
|
import std.string;
|
|
|
|
import std.system;
|
|
|
|
import std.typecons;
|
|
|
|
import std.utf;
|
|
|
|
|
2016-03-16 23:43:58 +00:00
|
|
|
import dyaml.stream;
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.anchor;
|
|
|
|
import dyaml.encoding;
|
2011-10-23 18:17:37 +00:00
|
|
|
import dyaml.escapes;
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.event;
|
|
|
|
import dyaml.exception;
|
2011-10-25 18:23:44 +00:00
|
|
|
import dyaml.fastcharsearch;
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.flags;
|
|
|
|
import dyaml.linebreak;
|
2011-10-20 08:34:34 +00:00
|
|
|
import dyaml.queue;
|
2011-10-27 21:13:14 +00:00
|
|
|
import dyaml.style;
|
2011-10-11 13:58:23 +00:00
|
|
|
import dyaml.tag;
|
2014-05-20 20:30:39 +00:00
|
|
|
import dyaml.tagdirective;
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
package:
|
|
|
|
|
2011-10-13 09:30:14 +00:00
|
|
|
/**
|
|
|
|
* Exception thrown at Emitter errors.
|
|
|
|
*
|
|
|
|
* See_Also:
|
|
|
|
* YAMLException
|
|
|
|
*/
|
2011-10-11 13:58:23 +00:00
|
|
|
class EmitterException : YAMLException
|
|
|
|
{
|
2011-10-13 09:30:14 +00:00
|
|
|
mixin ExceptionCtors;
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
2011-10-18 14:12:22 +00:00
|
|
|
private alias EmitterException Error;
|
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
//Stores results of analysis of a scalar, determining e.g. what scalar style to use.
|
2016-11-04 00:17:52 +00:00
|
|
|
struct ScalarAnalysis
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
//Scalar itself.
|
|
|
|
string scalar;
|
|
|
|
|
|
|
|
///Analysis results.
|
|
|
|
Flags!("empty", "multiline", "allowFlowPlain", "allowBlockPlain",
|
|
|
|
"allowSingleQuoted", "allowDoubleQuoted", "allowBlock", "isNull") flags;
|
|
|
|
}
|
|
|
|
|
2011-10-25 18:23:44 +00:00
|
|
|
///Quickly determines if a character is a newline.
|
|
|
|
private mixin FastCharSearch!"\n\u0085\u2028\u2029"d newlineSearch_;
|
|
|
|
|
2016-02-21 21:05:36 +00:00
|
|
|
// override the canFind added by the FastCharSearch mixins
|
|
|
|
private alias canFind = std.algorithm.canFind;
|
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
//Emits YAML events into a file/stream.
|
|
|
|
struct Emitter
|
|
|
|
{
|
|
|
|
private:
|
2011-11-16 02:10:29 +00:00
|
|
|
alias dyaml.tagdirective.TagDirective TagDirective;
|
2011-10-22 09:18:57 +00:00
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
///Default tag handle shortcuts and replacements.
|
2011-11-16 02:10:29 +00:00
|
|
|
static TagDirective[] defaultTagDirectives_ =
|
|
|
|
[TagDirective("!", "!"), TagDirective("!!", "tag:yaml.org,2002:")];
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
///Stream to write to.
|
2016-03-16 23:43:58 +00:00
|
|
|
YStream stream_;
|
2011-10-11 13:58:23 +00:00
|
|
|
///Encoding can be overriden by STREAM-START.
|
|
|
|
Encoding encoding_ = Encoding.UTF_8;
|
|
|
|
|
|
|
|
///Stack of states.
|
2011-10-23 22:46:35 +00:00
|
|
|
Array!(void delegate()) states_;
|
2011-10-11 13:58:23 +00:00
|
|
|
///Current state.
|
|
|
|
void delegate() state_;
|
|
|
|
|
|
|
|
///Event queue.
|
2011-10-20 08:34:34 +00:00
|
|
|
Queue!Event events_;
|
2011-10-11 13:58:23 +00:00
|
|
|
///Event we're currently emitting.
|
|
|
|
Event event_;
|
|
|
|
|
|
|
|
///Stack of previous indentation levels.
|
2011-10-26 04:30:10 +00:00
|
|
|
Array!int indents_;
|
2011-10-11 13:58:23 +00:00
|
|
|
///Current indentation level.
|
|
|
|
int indent_ = -1;
|
|
|
|
|
|
|
|
///Level of nesting in flow context. If 0, we're in block context.
|
|
|
|
uint flowLevel_ = 0;
|
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
/// Describes context (where we are in the document).
|
|
|
|
enum Context
|
|
|
|
{
|
|
|
|
/// Root node of a document.
|
|
|
|
Root,
|
|
|
|
/// Sequence.
|
|
|
|
Sequence,
|
|
|
|
/// Mapping.
|
|
|
|
MappingNoSimpleKey,
|
|
|
|
/// Mapping, in a simple key.
|
|
|
|
MappingSimpleKey
|
|
|
|
}
|
|
|
|
/// Current context.
|
|
|
|
Context context_;
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
///Characteristics of the last emitted character:
|
|
|
|
|
|
|
|
///Line.
|
|
|
|
uint line_ = 0;
|
|
|
|
///Column.
|
|
|
|
uint column_ = 0;
|
|
|
|
///Whitespace character?
|
|
|
|
bool whitespace_ = true;
|
|
|
|
///indentation space, '-', '?', or ':'?
|
|
|
|
bool indentation_ = true;
|
|
|
|
|
|
|
|
///Does the document require an explicit document indicator?
|
|
|
|
bool openEnded_;
|
|
|
|
|
|
|
|
///Formatting details.
|
|
|
|
|
|
|
|
///Canonical scalar format?
|
|
|
|
bool canonical_;
|
|
|
|
///Best indentation width.
|
|
|
|
uint bestIndent_ = 2;
|
|
|
|
///Best text width.
|
|
|
|
uint bestWidth_ = 80;
|
|
|
|
///Best line break character/s.
|
|
|
|
LineBreak bestLineBreak_;
|
|
|
|
|
2011-10-22 09:18:57 +00:00
|
|
|
///Tag directive handle - prefix pairs.
|
2011-11-16 02:10:29 +00:00
|
|
|
TagDirective[] tagDirectives_;
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
///Anchor/alias to process.
|
|
|
|
string preparedAnchor_ = null;
|
|
|
|
///Tag to process.
|
|
|
|
string preparedTag_ = null;
|
|
|
|
|
|
|
|
///Analysis result of the current scalar.
|
|
|
|
ScalarAnalysis analysis_;
|
|
|
|
///Style of the current scalar.
|
|
|
|
ScalarStyle style_ = ScalarStyle.Invalid;
|
|
|
|
|
|
|
|
public:
|
2011-10-30 19:24:43 +00:00
|
|
|
@disable int opCmp(ref Emitter);
|
|
|
|
@disable bool opEquals(ref Emitter);
|
2011-10-28 22:31:14 +00:00
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
/**
|
|
|
|
* Construct an emitter.
|
|
|
|
*
|
2016-03-16 23:43:58 +00:00
|
|
|
* Params: stream = YStream to write to. Must be writable.
|
2011-10-11 13:58:23 +00:00
|
|
|
* canonical = Write scalars in canonical form?
|
|
|
|
* indent = Indentation width.
|
|
|
|
* lineBreak = Line break character/s.
|
|
|
|
*/
|
2016-03-16 23:43:58 +00:00
|
|
|
this(YStream stream, const bool canonical, const int indent, const int width,
|
2012-12-27 19:56:15 +00:00
|
|
|
const LineBreak lineBreak) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
in{assert(stream.writeable, "Can't emit YAML to a non-writable stream");}
|
|
|
|
body
|
|
|
|
{
|
2011-10-23 22:46:35 +00:00
|
|
|
states_.reserve(32);
|
2011-10-26 04:30:10 +00:00
|
|
|
indents_.reserve(32);
|
2011-10-11 13:58:23 +00:00
|
|
|
stream_ = stream;
|
|
|
|
canonical_ = canonical;
|
|
|
|
state_ = &expectStreamStart;
|
|
|
|
|
|
|
|
if(indent > 1 && indent < 10){bestIndent_ = indent;}
|
|
|
|
if(width > bestIndent_ * 2) {bestWidth_ = width;}
|
|
|
|
bestLineBreak_ = lineBreak;
|
|
|
|
|
|
|
|
analysis_.flags.isNull = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Destroy the emitter.
|
2012-09-08 23:42:13 +00:00
|
|
|
@trusted ~this()
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
stream_ = null;
|
2014-07-19 02:17:18 +00:00
|
|
|
states_.destroy();
|
|
|
|
events_.destroy();
|
|
|
|
indents_.destroy();
|
|
|
|
tagDirectives_.destroy();
|
2011-10-20 08:34:34 +00:00
|
|
|
tagDirectives_ = null;
|
2014-07-19 02:17:18 +00:00
|
|
|
preparedAnchor_.destroy();
|
2011-10-20 08:34:34 +00:00
|
|
|
preparedAnchor_ = null;
|
2014-07-19 02:17:18 +00:00
|
|
|
preparedTag_.destroy();
|
2011-10-20 08:34:34 +00:00
|
|
|
preparedTag_ = null;
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Emit an event. Throws EmitterException on error.
|
2012-09-08 23:42:13 +00:00
|
|
|
void emit(Event event) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2011-10-20 08:34:34 +00:00
|
|
|
events_.push(event);
|
2011-10-11 13:58:23 +00:00
|
|
|
while(!needMoreEvents())
|
|
|
|
{
|
2011-10-20 08:34:34 +00:00
|
|
|
event_ = events_.pop();
|
2011-10-11 13:58:23 +00:00
|
|
|
state_();
|
2014-07-19 02:17:18 +00:00
|
|
|
event_.destroy();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
///Pop and return the newest state in states_.
|
2012-09-08 23:42:13 +00:00
|
|
|
void delegate() popState() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(states_.length > 0,
|
|
|
|
new YAMLException("Emitter: Need to pop a state but there are no states left"));
|
2011-10-23 22:46:35 +00:00
|
|
|
const result = states_.back;
|
|
|
|
states_.length = states_.length - 1;
|
2011-10-11 13:58:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Pop and return the newest indent in indents_.
|
2012-09-08 23:42:13 +00:00
|
|
|
int popIndent() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(indents_.length > 0,
|
2016-11-04 00:50:32 +00:00
|
|
|
new YAMLException("Emitter: Need to pop an indent level but there" ~
|
2011-10-11 13:58:23 +00:00
|
|
|
" are no indent levels left"));
|
2011-10-26 04:30:10 +00:00
|
|
|
const result = indents_.back;
|
|
|
|
indents_.length = indents_.length - 1;
|
2011-10-11 13:58:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write a string to the file/stream.
|
2012-09-13 19:37:28 +00:00
|
|
|
void writeString(const string str) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
try final switch(encoding_)
|
|
|
|
{
|
|
|
|
case Encoding.UTF_8:
|
|
|
|
stream_.writeExact(str.ptr, str.length * char.sizeof);
|
|
|
|
break;
|
|
|
|
case Encoding.UTF_16:
|
|
|
|
const buffer = to!wstring(str);
|
|
|
|
stream_.writeExact(buffer.ptr, buffer.length * wchar.sizeof);
|
|
|
|
break;
|
|
|
|
case Encoding.UTF_32:
|
|
|
|
const buffer = to!dstring(str);
|
|
|
|
stream_.writeExact(buffer.ptr, buffer.length * dchar.sizeof);
|
|
|
|
break;
|
|
|
|
}
|
2016-03-16 23:55:57 +00:00
|
|
|
catch(Exception e)
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new Error("Unable to write to stream: " ~ e.msg);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///In some cases, we wait for a few next events before emitting.
|
2012-09-08 23:42:13 +00:00
|
|
|
bool needMoreEvents() @trusted nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(events_.length == 0){return true;}
|
|
|
|
|
2012-09-08 23:42:13 +00:00
|
|
|
const event = events_.peek();
|
2011-10-20 08:34:34 +00:00
|
|
|
if(event.id == EventID.DocumentStart){return needEvents(1);}
|
|
|
|
if(event.id == EventID.SequenceStart){return needEvents(2);}
|
|
|
|
if(event.id == EventID.MappingStart) {return needEvents(3);}
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Determines if we need specified number of more events.
|
2012-09-08 23:42:13 +00:00
|
|
|
bool needEvents(in uint count) @system nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
int level = 0;
|
2011-10-20 08:34:34 +00:00
|
|
|
|
|
|
|
//Rather ugly, but good enough for now.
|
|
|
|
//Couldn't be bothered writing a range as events_ should eventually
|
|
|
|
//become a Phobos queue/linked list.
|
|
|
|
events_.startIteration();
|
|
|
|
events_.next();
|
|
|
|
while(!events_.iterationOver())
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-08 23:42:13 +00:00
|
|
|
const event = events_.next();
|
2011-10-23 22:46:35 +00:00
|
|
|
static starts = [EventID.DocumentStart, EventID.SequenceStart, EventID.MappingStart];
|
|
|
|
static ends = [EventID.DocumentEnd, EventID.SequenceEnd, EventID.MappingEnd];
|
2011-10-30 19:24:43 +00:00
|
|
|
if(starts.canFind(event.id)) {++level;}
|
|
|
|
else if(ends.canFind(event.id)){--level;}
|
2011-10-23 22:46:35 +00:00
|
|
|
else if(event.id == EventID.StreamStart){level = -1;}
|
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
if(level < 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return events_.length < (count + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
///Increase indentation level.
|
2012-09-13 23:16:05 +00:00
|
|
|
void increaseIndent(const Flag!"flow" flow = No.flow, const bool indentless = false) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
indents_ ~= indent_;
|
|
|
|
if(indent_ == -1)
|
|
|
|
{
|
|
|
|
indent_ = flow ? bestIndent_ : 0;
|
|
|
|
}
|
|
|
|
else if(!indentless)
|
|
|
|
{
|
|
|
|
indent_ += bestIndent_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Determines if the type of current event is as specified. Throws if no event.
|
2012-09-08 23:42:13 +00:00
|
|
|
bool eventTypeIs(in EventID id) const pure @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(!event_.isNull,
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Expected an event, but no event is available."));
|
2011-10-11 13:58:23 +00:00
|
|
|
return event_.id == id;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//States.
|
|
|
|
|
|
|
|
|
|
|
|
//Stream handlers.
|
|
|
|
|
|
|
|
///Handle start of a file/stream.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectStreamStart() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(eventTypeIs(EventID.StreamStart),
|
2016-03-16 23:43:58 +00:00
|
|
|
new Error("Expected YStreamStart, but got " ~ event_.idString));
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
encoding_ = event_.encoding;
|
|
|
|
writeStreamStart();
|
2012-09-13 23:21:01 +00:00
|
|
|
state_ = &expectDocumentStart!(Yes.first);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Expect nothing, throwing if we still have something.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectNothing() const @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
throw new Error("Expected nothing, but got " ~ event_.idString);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Document handlers.
|
|
|
|
|
|
|
|
///Handle start of a document.
|
2012-09-13 23:21:01 +00:00
|
|
|
void expectDocumentStart(Flag!"first" first)() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(eventTypeIs(EventID.DocumentStart) || eventTypeIs(EventID.StreamEnd),
|
2016-03-16 23:43:58 +00:00
|
|
|
new Error("Expected DocumentStart or YStreamEnd, but got "
|
2011-10-18 14:12:22 +00:00
|
|
|
~ event_.idString));
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
if(event_.id == EventID.DocumentStart)
|
|
|
|
{
|
|
|
|
const YAMLVersion = event_.value;
|
2011-11-16 02:10:29 +00:00
|
|
|
auto tagDirectives = event_.tagDirectives;
|
|
|
|
if(openEnded_ && (YAMLVersion !is null || tagDirectives !is null))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("...", Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
writeIndent();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(YAMLVersion !is null)
|
|
|
|
{
|
|
|
|
writeVersionDirective(prepareVersion(YAMLVersion));
|
|
|
|
}
|
|
|
|
|
2011-11-16 02:10:29 +00:00
|
|
|
if(tagDirectives !is null)
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2011-11-16 02:10:29 +00:00
|
|
|
tagDirectives_ = tagDirectives;
|
|
|
|
sort!"icmp(a.handle, b.handle) < 0"(tagDirectives_);
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
foreach(ref pair; tagDirectives_)
|
|
|
|
{
|
2011-10-22 09:18:57 +00:00
|
|
|
writeTagDirective(prepareTagHandle(pair.handle),
|
|
|
|
prepareTagPrefix(pair.prefix));
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-16 02:10:29 +00:00
|
|
|
bool eq(ref TagDirective a, ref TagDirective b){return a.handle == b.handle;}
|
2011-10-22 09:18:57 +00:00
|
|
|
//Add any default tag directives that have not been overriden.
|
2011-10-25 18:23:44 +00:00
|
|
|
foreach(ref def; defaultTagDirectives_)
|
2011-10-22 09:18:57 +00:00
|
|
|
{
|
2011-10-25 18:23:44 +00:00
|
|
|
if(!std.algorithm.canFind!eq(tagDirectives_, def))
|
|
|
|
{
|
|
|
|
tagDirectives_ ~= def;
|
|
|
|
}
|
2011-10-22 09:18:57 +00:00
|
|
|
}
|
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
const implicit = first && !event_.explicitDocument && !canonical_ &&
|
2011-11-16 02:10:29 +00:00
|
|
|
YAMLVersion is null && tagDirectives is null &&
|
2011-10-11 13:58:23 +00:00
|
|
|
!checkEmptyDocument();
|
|
|
|
if(!implicit)
|
|
|
|
{
|
|
|
|
writeIndent();
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("---", Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
if(canonical_){writeIndent();}
|
|
|
|
}
|
2012-09-13 23:16:05 +00:00
|
|
|
state_ = &expectRootNode;
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
else if(event_.id == EventID.StreamEnd)
|
|
|
|
{
|
|
|
|
if(openEnded_)
|
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("...", Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
writeIndent();
|
|
|
|
}
|
|
|
|
writeStreamEnd();
|
|
|
|
state_ = &expectNothing;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Handle end of a document.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectDocumentEnd() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(eventTypeIs(EventID.DocumentEnd),
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Expected DocumentEnd, but got " ~ event_.idString));
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
writeIndent();
|
|
|
|
if(event_.explicitDocument)
|
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("...", Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
writeIndent();
|
|
|
|
}
|
|
|
|
stream_.flush();
|
2012-09-13 23:21:01 +00:00
|
|
|
state_ = &expectDocumentStart!(No.first);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle the root node of a document.
|
2012-09-13 23:16:05 +00:00
|
|
|
void expectRootNode() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
states_ ~= &expectDocumentEnd;
|
2012-09-13 23:16:05 +00:00
|
|
|
expectNode(Context.Root);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
///Handle a mapping node.
|
|
|
|
//
|
|
|
|
//Params: simpleKey = Are we in a simple key?
|
|
|
|
void expectMappingNode(const bool simpleKey = false)
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
expectNode(simpleKey ? Context.MappingSimpleKey : Context.MappingNoSimpleKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a sequence node.
|
|
|
|
void expectSequenceNode()
|
|
|
|
{
|
|
|
|
expectNode(Context.Sequence);
|
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a new node. Context specifies where in the document we are.
|
|
|
|
void expectNode(const Context context) @trusted
|
|
|
|
{
|
|
|
|
context_ = context;
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
const flowCollection = event_.collectionStyle == CollectionStyle.Flow;
|
|
|
|
|
|
|
|
switch(event_.id)
|
|
|
|
{
|
|
|
|
case EventID.Alias: expectAlias(); break;
|
|
|
|
case EventID.Scalar:
|
|
|
|
processAnchor("&");
|
|
|
|
processTag();
|
|
|
|
expectScalar();
|
|
|
|
break;
|
|
|
|
case EventID.SequenceStart:
|
|
|
|
processAnchor("&");
|
|
|
|
processTag();
|
|
|
|
if(flowLevel_ > 0 || canonical_ || flowCollection || checkEmptySequence())
|
|
|
|
{
|
|
|
|
expectFlowSequence();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
expectBlockSequence();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EventID.MappingStart:
|
|
|
|
processAnchor("&");
|
|
|
|
processTag();
|
|
|
|
if(flowLevel_ > 0 || canonical_ || flowCollection || checkEmptyMapping())
|
|
|
|
{
|
|
|
|
expectFlowMapping();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
expectBlockMapping();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2016-11-04 00:50:32 +00:00
|
|
|
throw new Error("Expected Alias, Scalar, SequenceStart or " ~
|
2011-10-18 14:12:22 +00:00
|
|
|
"MappingStart, but got: " ~ event_.idString);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
///Handle an alias.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectAlias() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
enforce(!event_.anchor.isNull(), new Error("Anchor is not specified for alias"));
|
2011-10-11 13:58:23 +00:00
|
|
|
processAnchor("*");
|
|
|
|
state_ = popState();
|
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a scalar.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectScalar() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
increaseIndent(Yes.flow);
|
2011-10-11 13:58:23 +00:00
|
|
|
processScalar();
|
|
|
|
indent_ = popIndent();
|
|
|
|
state_ = popState();
|
|
|
|
}
|
|
|
|
|
|
|
|
//Flow sequence handlers.
|
|
|
|
|
|
|
|
///Handle a flow sequence.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectFlowSequence() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("[", Yes.needWhitespace, Yes.whitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
++flowLevel_;
|
2012-09-13 23:16:05 +00:00
|
|
|
increaseIndent(Yes.flow);
|
2012-09-13 23:21:01 +00:00
|
|
|
state_ = &expectFlowSequenceItem!(Yes.first);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a flow sequence item.
|
2012-09-13 23:21:01 +00:00
|
|
|
void expectFlowSequenceItem(Flag!"first" first)() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(event_.id == EventID.SequenceEnd)
|
|
|
|
{
|
|
|
|
indent_ = popIndent();
|
|
|
|
--flowLevel_;
|
|
|
|
static if(!first) if(canonical_)
|
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(",", No.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
writeIndent();
|
|
|
|
}
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("]", No.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
state_ = popState();
|
|
|
|
return;
|
|
|
|
}
|
2012-09-13 23:16:05 +00:00
|
|
|
static if(!first){writeIndicator(",", No.needWhitespace);}
|
2011-10-11 13:58:23 +00:00
|
|
|
if(canonical_ || column_ > bestWidth_){writeIndent();}
|
2012-09-13 23:21:01 +00:00
|
|
|
states_ ~= &expectFlowSequenceItem!(No.first);
|
2012-09-13 23:16:05 +00:00
|
|
|
expectSequenceNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Flow mapping handlers.
|
|
|
|
|
|
|
|
///Handle a flow mapping.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectFlowMapping() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("{", Yes.needWhitespace, Yes.whitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
++flowLevel_;
|
2012-09-13 23:16:05 +00:00
|
|
|
increaseIndent(Yes.flow);
|
2012-09-13 23:21:01 +00:00
|
|
|
state_ = &expectFlowMappingKey!(Yes.first);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a key in a flow mapping.
|
2012-09-13 23:21:01 +00:00
|
|
|
void expectFlowMappingKey(Flag!"first" first)() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(event_.id == EventID.MappingEnd)
|
|
|
|
{
|
|
|
|
indent_ = popIndent();
|
|
|
|
--flowLevel_;
|
|
|
|
static if (!first) if(canonical_)
|
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(",", No.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
writeIndent();
|
|
|
|
}
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("}", No.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
state_ = popState();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
static if(!first){writeIndicator(",", No.needWhitespace);}
|
2011-10-11 13:58:23 +00:00
|
|
|
if(canonical_ || column_ > bestWidth_){writeIndent();}
|
|
|
|
if(!canonical_ && checkSimpleKey())
|
|
|
|
{
|
|
|
|
states_ ~= &expectFlowMappingSimpleValue;
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode(true);
|
2011-10-11 13:58:23 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("?", Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
states_ ~= &expectFlowMappingValue;
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a simple value in a flow mapping.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectFlowMappingSimpleValue() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(":", No.needWhitespace);
|
2012-09-13 23:21:01 +00:00
|
|
|
states_ ~= &expectFlowMappingKey!(No.first);
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a complex value in a flow mapping.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectFlowMappingValue() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(canonical_ || column_ > bestWidth_){writeIndent();}
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(":", Yes.needWhitespace);
|
2012-09-13 23:21:01 +00:00
|
|
|
states_ ~= &expectFlowMappingKey!(No.first);
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Block sequence handlers.
|
|
|
|
|
|
|
|
///Handle a block sequence.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectBlockSequence() @safe
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
const indentless = (context_ == Context.MappingNoSimpleKey ||
|
|
|
|
context_ == Context.MappingSimpleKey) && !indentation_;
|
|
|
|
increaseIndent(No.flow, indentless);
|
2012-09-13 23:21:01 +00:00
|
|
|
state_ = &expectBlockSequenceItem!(Yes.first);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a block sequence item.
|
2012-09-13 23:21:01 +00:00
|
|
|
void expectBlockSequenceItem(Flag!"first" first)() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
static if(!first) if(event_.id == EventID.SequenceEnd)
|
|
|
|
{
|
|
|
|
indent_ = popIndent();
|
|
|
|
state_ = popState();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeIndent();
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("-", Yes.needWhitespace, No.whitespace, Yes.indentation);
|
2012-09-13 23:21:01 +00:00
|
|
|
states_ ~= &expectBlockSequenceItem!(No.first);
|
2012-09-13 23:16:05 +00:00
|
|
|
expectSequenceNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Block mapping handlers.
|
|
|
|
|
|
|
|
///Handle a block mapping.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectBlockMapping() @safe
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
increaseIndent(No.flow);
|
2012-09-13 23:21:01 +00:00
|
|
|
state_ = &expectBlockMappingKey!(Yes.first);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a key in a block mapping.
|
2012-09-13 23:21:01 +00:00
|
|
|
void expectBlockMappingKey(Flag!"first" first)() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
static if(!first) if(event_.id == EventID.MappingEnd)
|
|
|
|
{
|
|
|
|
indent_ = popIndent();
|
|
|
|
state_ = popState();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeIndent();
|
|
|
|
if(checkSimpleKey())
|
|
|
|
{
|
|
|
|
states_ ~= &expectBlockMappingSimpleValue;
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode(true);
|
2011-10-11 13:58:23 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator("?", Yes.needWhitespace, No.whitespace, Yes.indentation);
|
2011-10-11 13:58:23 +00:00
|
|
|
states_ ~= &expectBlockMappingValue;
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a simple value in a block mapping.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectBlockMappingSimpleValue() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(":", No.needWhitespace);
|
2012-09-13 23:21:01 +00:00
|
|
|
states_ ~= &expectBlockMappingKey!(No.first);
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Handle a complex value in a block mapping.
|
2012-09-08 23:42:13 +00:00
|
|
|
void expectBlockMappingValue() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeIndent();
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(":", Yes.needWhitespace, No.whitespace, Yes.indentation);
|
2012-09-13 23:21:01 +00:00
|
|
|
states_ ~= &expectBlockMappingKey!(No.first);
|
2012-09-13 23:16:05 +00:00
|
|
|
expectMappingNode();
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Checkers.
|
|
|
|
|
|
|
|
///Check if an empty sequence is next.
|
2012-09-08 23:42:13 +00:00
|
|
|
bool checkEmptySequence() const @trusted pure nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
return event_.id == EventID.SequenceStart && events_.length > 0
|
2011-10-20 08:34:34 +00:00
|
|
|
&& events_.peek().id == EventID.SequenceEnd;
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Check if an empty mapping is next.
|
2012-09-08 23:42:13 +00:00
|
|
|
bool checkEmptyMapping() const @trusted pure nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
return event_.id == EventID.MappingStart && events_.length > 0
|
2011-10-20 08:34:34 +00:00
|
|
|
&& events_.peek().id == EventID.MappingEnd;
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Check if an empty document is next.
|
2012-09-08 23:42:13 +00:00
|
|
|
bool checkEmptyDocument() const @trusted pure nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(event_.id != EventID.DocumentStart || events_.length == 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-09-08 23:42:13 +00:00
|
|
|
const event = events_.peek();
|
2011-10-30 19:24:43 +00:00
|
|
|
const emptyScalar = event.id == EventID.Scalar && event.anchor.isNull() &&
|
|
|
|
event.tag.isNull() && event.implicit && event.value == "";
|
2011-10-11 13:58:23 +00:00
|
|
|
return emptyScalar;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Check if a simple key is next.
|
2012-09-08 23:42:13 +00:00
|
|
|
bool checkSimpleKey() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
uint length = 0;
|
|
|
|
const id = event_.id;
|
|
|
|
const scalar = id == EventID.Scalar;
|
2011-10-23 22:46:35 +00:00
|
|
|
const collectionStart = id == EventID.MappingStart ||
|
|
|
|
id == EventID.SequenceStart;
|
2011-10-11 13:58:23 +00:00
|
|
|
|
2011-10-13 20:04:37 +00:00
|
|
|
if((id == EventID.Alias || scalar || collectionStart)
|
|
|
|
&& !event_.anchor.isNull())
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(preparedAnchor_ is null)
|
|
|
|
{
|
|
|
|
preparedAnchor_ = prepareAnchor(event_.anchor);
|
|
|
|
}
|
|
|
|
length += preparedAnchor_.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((scalar || collectionStart) && !event_.tag.isNull())
|
|
|
|
{
|
|
|
|
if(preparedTag_ is null){preparedTag_ = prepareTag(event_.tag);}
|
|
|
|
length += preparedTag_.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(scalar)
|
|
|
|
{
|
|
|
|
if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
|
|
|
|
length += analysis_.scalar.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(length >= 128){return false;}
|
|
|
|
|
|
|
|
return id == EventID.Alias ||
|
|
|
|
(scalar && !analysis_.flags.empty && !analysis_.flags.multiline) ||
|
|
|
|
checkEmptySequence() ||
|
|
|
|
checkEmptyMapping();
|
|
|
|
}
|
|
|
|
|
|
|
|
///Process and write a scalar.
|
2012-09-08 23:42:13 +00:00
|
|
|
void processScalar() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
|
|
|
|
if(style_ == ScalarStyle.Invalid)
|
|
|
|
{
|
|
|
|
style_ = chooseScalarStyle();
|
|
|
|
}
|
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
//if(analysis_.flags.multiline && (context_ != Context.MappingSimpleKey) &&
|
2011-10-11 13:58:23 +00:00
|
|
|
// ([ScalarStyle.Invalid, ScalarStyle.Plain, ScalarStyle.SingleQuoted, ScalarStyle.DoubleQuoted)
|
|
|
|
// .canFind(style_))
|
|
|
|
//{
|
|
|
|
// writeIndent();
|
|
|
|
//}
|
2012-09-13 23:16:05 +00:00
|
|
|
auto writer = ScalarWriter(this, analysis_.scalar,
|
|
|
|
context_ != Context.MappingSimpleKey);
|
2011-10-11 13:58:23 +00:00
|
|
|
with(writer) final switch(style_)
|
|
|
|
{
|
|
|
|
case ScalarStyle.Invalid: assert(false);
|
|
|
|
case ScalarStyle.DoubleQuoted: writeDoubleQuoted(); break;
|
|
|
|
case ScalarStyle.SingleQuoted: writeSingleQuoted(); break;
|
|
|
|
case ScalarStyle.Folded: writeFolded(); break;
|
|
|
|
case ScalarStyle.Literal: writeLiteral(); break;
|
|
|
|
case ScalarStyle.Plain: writePlain(); break;
|
|
|
|
}
|
|
|
|
analysis_.flags.isNull = true;
|
|
|
|
style_ = ScalarStyle.Invalid;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Process and write an anchor/alias.
|
2012-09-13 19:37:28 +00:00
|
|
|
void processAnchor(const string indicator) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(event_.anchor.isNull())
|
|
|
|
{
|
|
|
|
preparedAnchor_ = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(preparedAnchor_ is null)
|
|
|
|
{
|
|
|
|
preparedAnchor_ = prepareAnchor(event_.anchor);
|
|
|
|
}
|
|
|
|
if(preparedAnchor_ !is null && preparedAnchor_ != "")
|
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(indicator, Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
writeString(preparedAnchor_);
|
|
|
|
}
|
|
|
|
preparedAnchor_ = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Process and write a tag.
|
2012-09-08 23:42:13 +00:00
|
|
|
void processTag() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
Tag tag = event_.tag;
|
|
|
|
|
|
|
|
if(event_.id == EventID.Scalar)
|
|
|
|
{
|
|
|
|
if(style_ == ScalarStyle.Invalid){style_ = chooseScalarStyle();}
|
|
|
|
if((!canonical_ || tag.isNull()) &&
|
|
|
|
(style_ == ScalarStyle.Plain ? event_.implicit : event_.implicit_2))
|
|
|
|
{
|
|
|
|
preparedTag_ = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(event_.implicit && tag.isNull())
|
|
|
|
{
|
|
|
|
tag = Tag("!");
|
|
|
|
preparedTag_ = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if((!canonical_ || tag.isNull()) && event_.implicit)
|
|
|
|
{
|
|
|
|
preparedTag_ = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-10-18 14:12:22 +00:00
|
|
|
enforce(!tag.isNull(), new Error("Tag is not specified"));
|
2011-10-11 13:58:23 +00:00
|
|
|
if(preparedTag_ is null){preparedTag_ = prepareTag(tag);}
|
|
|
|
if(preparedTag_ !is null && preparedTag_ != "")
|
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
writeIndicator(preparedTag_, Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
preparedTag_ = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Determine style to write the current scalar in.
|
2012-09-08 23:42:13 +00:00
|
|
|
ScalarStyle chooseScalarStyle() @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
|
|
|
|
|
|
|
|
const style = event_.scalarStyle;
|
2011-10-23 22:46:35 +00:00
|
|
|
const invalidOrPlain = style == ScalarStyle.Invalid || style == ScalarStyle.Plain;
|
|
|
|
const block = style == ScalarStyle.Literal || style == ScalarStyle.Folded;
|
2011-10-11 13:58:23 +00:00
|
|
|
const singleQuoted = style == ScalarStyle.SingleQuoted;
|
|
|
|
const doubleQuoted = style == ScalarStyle.DoubleQuoted;
|
|
|
|
|
|
|
|
const allowPlain = flowLevel_ > 0 ? analysis_.flags.allowFlowPlain
|
|
|
|
: analysis_.flags.allowBlockPlain;
|
|
|
|
//simple empty or multiline scalars can't be written in plain style
|
2012-09-13 23:16:05 +00:00
|
|
|
const simpleNonPlain = (context_ == Context.MappingSimpleKey) &&
|
2011-10-11 13:58:23 +00:00
|
|
|
(analysis_.flags.empty || analysis_.flags.multiline);
|
|
|
|
|
|
|
|
if(doubleQuoted || canonical_)
|
|
|
|
{
|
|
|
|
return ScalarStyle.DoubleQuoted;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(invalidOrPlain && event_.implicit && !simpleNonPlain && allowPlain)
|
|
|
|
{
|
|
|
|
return ScalarStyle.Plain;
|
|
|
|
}
|
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
if(block && flowLevel_ == 0 && context_ != Context.MappingSimpleKey &&
|
2011-10-11 13:58:23 +00:00
|
|
|
analysis_.flags.allowBlock)
|
|
|
|
{
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((invalidOrPlain || singleQuoted) &&
|
|
|
|
analysis_.flags.allowSingleQuoted &&
|
2012-09-13 23:16:05 +00:00
|
|
|
!(context_ == Context.MappingSimpleKey && analysis_.flags.multiline))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
return ScalarStyle.SingleQuoted;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ScalarStyle.DoubleQuoted;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Prepare YAML version string for output.
|
2012-09-13 19:37:28 +00:00
|
|
|
static string prepareVersion(const string YAMLVersion) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(YAMLVersion.split(".")[0] == "1",
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Unsupported YAML version: " ~ YAMLVersion));
|
2011-10-11 13:58:23 +00:00
|
|
|
return YAMLVersion;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Encode an Unicode character for tag directive and write it to writer.
|
2012-09-08 23:42:13 +00:00
|
|
|
static void encodeChar(Writer)(ref Writer writer, in dchar c) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
char[4] data;
|
2011-10-30 19:24:43 +00:00
|
|
|
const bytes = encode(data, c);
|
2011-10-11 13:58:23 +00:00
|
|
|
//For each byte add string in format %AB , where AB are hex digits of the byte.
|
|
|
|
foreach(const char b; data[0 .. bytes])
|
|
|
|
{
|
|
|
|
formattedWrite(writer, "%%%02X", cast(ubyte)b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Prepare tag directive handle for output.
|
2012-09-13 19:37:28 +00:00
|
|
|
static string prepareTagHandle(const string handle) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(handle !is null && handle != "",
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Tag handle must not be empty"));
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
if(handle.length > 1) foreach(const dchar c; handle[1 .. $ - 1])
|
|
|
|
{
|
2011-10-23 22:46:35 +00:00
|
|
|
enforce(isAlphaNum(c) || "-_"d.canFind(c),
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Invalid character: " ~ to!string(c) ~
|
|
|
|
" in tag handle " ~ handle));
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Prepare tag directive prefix for output.
|
2012-09-13 19:37:28 +00:00
|
|
|
static string prepareTagPrefix(const string prefix) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(prefix !is null && prefix != "",
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Tag prefix must not be empty"));
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
auto appender = appender!string();
|
|
|
|
const offset = prefix[0] == '!' ? 1 : 0;
|
|
|
|
size_t start = 0;
|
|
|
|
size_t end = 0;
|
|
|
|
|
|
|
|
foreach(const size_t i, const dchar c; prefix)
|
|
|
|
{
|
|
|
|
const size_t idx = i + offset;
|
2011-10-23 22:46:35 +00:00
|
|
|
if(isAlphaNum(c) || "-;/?:@&=+$,_.!~*\'()[]%"d.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
end = idx + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(start < idx){appender.put(prefix[start .. idx]);}
|
|
|
|
start = end = idx + 1;
|
|
|
|
|
|
|
|
encodeChar(appender, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
end = min(end, prefix.length);
|
|
|
|
if(start < end){appender.put(prefix[start .. end]);}
|
|
|
|
return appender.data;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Prepare tag for output.
|
2012-09-08 23:42:13 +00:00
|
|
|
string prepareTag(in Tag tag) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
enforce(!tag.isNull(), new Error("Tag must not be empty"));
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
string tagString = tag.get;
|
|
|
|
if(tagString == "!"){return tagString;}
|
|
|
|
string handle = null;
|
|
|
|
string suffix = tagString;
|
|
|
|
|
|
|
|
//Sort lexicographically by prefix.
|
2011-11-16 02:10:29 +00:00
|
|
|
sort!"icmp(a.prefix, b.prefix) < 0"(tagDirectives_);
|
2011-10-11 13:58:23 +00:00
|
|
|
foreach(ref pair; tagDirectives_)
|
|
|
|
{
|
2011-11-16 02:10:29 +00:00
|
|
|
auto prefix = pair.prefix;
|
2011-10-11 13:58:23 +00:00
|
|
|
if(tagString.startsWith(prefix) &&
|
|
|
|
(prefix != "!" || prefix.length < tagString.length))
|
|
|
|
{
|
2011-11-16 02:10:29 +00:00
|
|
|
handle = pair.handle;
|
2011-10-11 13:58:23 +00:00
|
|
|
suffix = tagString[prefix.length .. $];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto appender = appender!string();
|
|
|
|
appender.put(handle !is null && handle != "" ? handle : "!<");
|
|
|
|
size_t start = 0;
|
|
|
|
size_t end = 0;
|
|
|
|
foreach(const dchar c; suffix)
|
|
|
|
{
|
2011-10-23 22:46:35 +00:00
|
|
|
if(isAlphaNum(c) || "-;/?:@&=+$,_.~*\'()[]"d.canFind(c) ||
|
2011-10-11 13:58:23 +00:00
|
|
|
(c == '!' && handle != "!"))
|
|
|
|
{
|
|
|
|
++end;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if(start < end){appender.put(suffix[start .. end]);}
|
|
|
|
start = end = end + 1;
|
|
|
|
|
|
|
|
encodeChar(appender, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(start < end){appender.put(suffix[start .. end]);}
|
|
|
|
if(handle is null || handle == ""){appender.put(">");}
|
|
|
|
|
|
|
|
return appender.data;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Prepare anchor for output.
|
2012-09-13 23:16:05 +00:00
|
|
|
static string prepareAnchor(const Anchor anchor) @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
enforce(!anchor.isNull() && anchor.get != "",
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Anchor must not be empty"));
|
2011-10-11 13:58:23 +00:00
|
|
|
const str = anchor.get;
|
|
|
|
foreach(const dchar c; str)
|
|
|
|
{
|
2011-10-23 22:46:35 +00:00
|
|
|
enforce(isAlphaNum(c) || "-_"d.canFind(c),
|
2011-10-18 14:12:22 +00:00
|
|
|
new Error("Invalid character: " ~ to!string(c) ~ " in anchor: " ~ str));
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Analyze specifed scalar and return the analysis result.
|
2012-09-08 23:42:13 +00:00
|
|
|
static ScalarAnalysis analyzeScalar(string scalar) @safe
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
ScalarAnalysis analysis;
|
|
|
|
analysis.flags.isNull = false;
|
|
|
|
analysis.scalar = scalar;
|
|
|
|
|
|
|
|
//Empty scalar is a special case.
|
|
|
|
with(analysis.flags) if(scalar is null || scalar == "")
|
|
|
|
{
|
|
|
|
empty = true;
|
|
|
|
multiline = false;
|
|
|
|
allowFlowPlain = false;
|
|
|
|
allowBlockPlain = true;
|
|
|
|
allowSingleQuoted = true;
|
|
|
|
allowDoubleQuoted = true;
|
|
|
|
allowBlock = false;
|
|
|
|
return analysis;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Indicators and special characters (All false by default).
|
|
|
|
bool blockIndicators, flowIndicators, lineBreaks, specialCharacters;
|
|
|
|
|
|
|
|
//Important whitespace combinations (All false by default).
|
|
|
|
bool leadingSpace, leadingBreak, trailingSpace, trailingBreak,
|
|
|
|
breakSpace, spaceBreak;
|
|
|
|
|
|
|
|
//Check document indicators.
|
|
|
|
if(scalar.startsWith("---", "..."))
|
|
|
|
{
|
|
|
|
blockIndicators = flowIndicators = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//First character or preceded by a whitespace.
|
|
|
|
bool preceededByWhitespace = true;
|
|
|
|
|
|
|
|
//Last character or followed by a whitespace.
|
|
|
|
bool followedByWhitespace = scalar.length == 1 ||
|
2011-10-23 22:46:35 +00:00
|
|
|
" \t\0\n\r\u0085\u2028\u2029"d.canFind(scalar[1]);
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
//The previous character is a space/break (false by default).
|
|
|
|
bool previousSpace, previousBreak;
|
|
|
|
|
|
|
|
foreach(const size_t index, const dchar c; scalar)
|
|
|
|
{
|
2011-10-25 18:23:44 +00:00
|
|
|
mixin FastCharSearch!("#,[]{}&*!|>\'\"%@`"d, 128) specialCharSearch;
|
|
|
|
mixin FastCharSearch!(",?[]{}"d, 128) flowIndicatorSearch;
|
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
//Check for indicators.
|
|
|
|
if(index == 0)
|
|
|
|
{
|
|
|
|
//Leading indicators are special characters.
|
2011-10-25 18:23:44 +00:00
|
|
|
if(specialCharSearch.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
flowIndicators = blockIndicators = true;
|
|
|
|
}
|
2011-10-25 18:23:44 +00:00
|
|
|
if(':' == c || '?' == c)
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
flowIndicators = true;
|
|
|
|
if(followedByWhitespace){blockIndicators = true;}
|
|
|
|
}
|
|
|
|
if(c == '-' && followedByWhitespace)
|
|
|
|
{
|
|
|
|
flowIndicators = blockIndicators = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Some indicators cannot appear within a scalar as well.
|
2011-10-25 18:23:44 +00:00
|
|
|
if(flowIndicatorSearch.canFind(c)){flowIndicators = true;}
|
2011-10-11 13:58:23 +00:00
|
|
|
if(c == ':')
|
|
|
|
{
|
|
|
|
flowIndicators = true;
|
|
|
|
if(followedByWhitespace){blockIndicators = true;}
|
|
|
|
}
|
|
|
|
if(c == '#' && preceededByWhitespace)
|
|
|
|
{
|
|
|
|
flowIndicators = blockIndicators = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check for line breaks, special, and unicode characters.
|
2011-10-25 18:23:44 +00:00
|
|
|
if(newlineSearch_.canFind(c)){lineBreaks = true;}
|
2011-10-11 13:58:23 +00:00
|
|
|
if(!(c == '\n' || (c >= '\x20' && c <= '\x7E')) &&
|
|
|
|
!((c == '\u0085' || (c >= '\xA0' && c <= '\uD7FF') ||
|
|
|
|
(c >= '\uE000' && c <= '\uFFFD')) && c != '\uFEFF'))
|
|
|
|
{
|
|
|
|
specialCharacters = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Detect important whitespace combinations.
|
|
|
|
if(c == ' ')
|
|
|
|
{
|
|
|
|
if(index == 0){leadingSpace = true;}
|
|
|
|
if(index == scalar.length - 1){trailingSpace = true;}
|
|
|
|
if(previousBreak){breakSpace = true;}
|
|
|
|
previousSpace = true;
|
|
|
|
previousBreak = false;
|
|
|
|
}
|
2011-10-25 18:23:44 +00:00
|
|
|
else if(newlineSearch_.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(index == 0){leadingBreak = true;}
|
|
|
|
if(index == scalar.length - 1){trailingBreak = true;}
|
|
|
|
if(previousSpace){spaceBreak = true;}
|
|
|
|
previousSpace = false;
|
|
|
|
previousBreak = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
previousSpace = previousBreak = false;
|
|
|
|
}
|
|
|
|
|
2011-10-25 18:23:44 +00:00
|
|
|
mixin FastCharSearch! "\0\n\r\u0085\u2028\u2029 \t"d spaceSearch;
|
2011-10-11 13:58:23 +00:00
|
|
|
//Prepare for the next character.
|
2011-10-25 18:23:44 +00:00
|
|
|
preceededByWhitespace = spaceSearch.canFind(c);
|
2011-10-11 13:58:23 +00:00
|
|
|
followedByWhitespace = index + 2 >= scalar.length ||
|
2011-10-25 18:23:44 +00:00
|
|
|
spaceSearch.canFind(scalar[index + 2]);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
with(analysis.flags)
|
|
|
|
{
|
|
|
|
//Let's decide what styles are allowed.
|
|
|
|
allowFlowPlain = allowBlockPlain = allowSingleQuoted
|
|
|
|
= allowDoubleQuoted = allowBlock = true;
|
|
|
|
|
|
|
|
//Leading and trailing whitespaces are bad for plain scalars.
|
|
|
|
if(leadingSpace || leadingBreak || trailingSpace || trailingBreak)
|
|
|
|
{
|
|
|
|
allowFlowPlain = allowBlockPlain = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//We do not permit trailing spaces for block scalars.
|
|
|
|
if(trailingSpace){allowBlock = false;}
|
|
|
|
|
|
|
|
//Spaces at the beginning of a new line are only acceptable for block
|
|
|
|
//scalars.
|
|
|
|
if(breakSpace)
|
|
|
|
{
|
|
|
|
allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Spaces followed by breaks, as well as special character are only
|
|
|
|
//allowed for double quoted scalars.
|
|
|
|
if(spaceBreak || specialCharacters)
|
|
|
|
{
|
|
|
|
allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Although the plain scalar writer supports breaks, we never emit
|
|
|
|
//multiline plain scalars.
|
|
|
|
if(lineBreaks){allowFlowPlain = allowBlockPlain = false;}
|
|
|
|
|
|
|
|
//Flow indicators are forbidden for flow plain scalars.
|
|
|
|
if(flowIndicators){allowFlowPlain = false;}
|
|
|
|
|
|
|
|
//Block indicators are forbidden for block plain scalars.
|
|
|
|
if(blockIndicators){allowBlockPlain = false;}
|
|
|
|
|
|
|
|
empty = false;
|
|
|
|
multiline = lineBreaks;
|
|
|
|
}
|
|
|
|
|
|
|
|
return analysis;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Writers.
|
|
|
|
|
|
|
|
///Start the YAML stream (write the unicode byte order mark).
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeStreamStart() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
immutable(ubyte)[] bom;
|
|
|
|
//Write BOM (always, even for UTF-8)
|
|
|
|
final switch(encoding_)
|
|
|
|
{
|
|
|
|
case Encoding.UTF_8:
|
|
|
|
bom = ByteOrderMarks[BOM.UTF8];
|
|
|
|
break;
|
|
|
|
case Encoding.UTF_16:
|
|
|
|
bom = std.system.endian == Endian.littleEndian
|
|
|
|
? ByteOrderMarks[BOM.UTF16LE]
|
|
|
|
: ByteOrderMarks[BOM.UTF16BE];
|
|
|
|
break;
|
|
|
|
case Encoding.UTF_32:
|
|
|
|
bom = std.system.endian == Endian.littleEndian
|
|
|
|
? ByteOrderMarks[BOM.UTF32LE]
|
|
|
|
: ByteOrderMarks[BOM.UTF32BE];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-10-18 14:12:22 +00:00
|
|
|
enforce(stream_.write(bom) == bom.length, new Error("Unable to write to stream"));
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///End the YAML stream.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeStreamEnd() @system {stream_.flush();}
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
///Write an indicator (e.g. ":", "[", ">", etc.).
|
2012-09-13 23:16:05 +00:00
|
|
|
void writeIndicator(const string indicator,
|
|
|
|
const Flag!"needWhitespace" needWhitespace,
|
|
|
|
const Flag!"whitespace" whitespace = No.whitespace,
|
|
|
|
const Flag!"indentation" indentation = No.indentation) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
const bool prefixSpace = !whitespace_ && needWhitespace;
|
2012-09-13 23:16:05 +00:00
|
|
|
whitespace_ = whitespace;
|
2011-10-11 13:58:23 +00:00
|
|
|
indentation_ = indentation_ && indentation;
|
2012-09-13 23:16:05 +00:00
|
|
|
openEnded_ = false;
|
2011-10-11 13:58:23 +00:00
|
|
|
column_ += indicator.length;
|
|
|
|
if(prefixSpace)
|
|
|
|
{
|
|
|
|
++column_;
|
|
|
|
writeString(" ");
|
|
|
|
}
|
|
|
|
writeString(indicator);
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write indentation.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeIndent() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
const indent = indent_ == -1 ? 0 : indent_;
|
|
|
|
|
|
|
|
if(!indentation_ || column_ > indent || (column_ == indent && !whitespace_))
|
|
|
|
{
|
|
|
|
writeLineBreak();
|
|
|
|
}
|
|
|
|
if(column_ < indent)
|
|
|
|
{
|
|
|
|
whitespace_ = true;
|
|
|
|
|
|
|
|
//Used to avoid allocation of arbitrary length strings.
|
|
|
|
static immutable spaces = " ";
|
|
|
|
size_t numSpaces = indent - column_;
|
|
|
|
column_ = indent;
|
|
|
|
while(numSpaces >= spaces.length)
|
|
|
|
{
|
|
|
|
writeString(spaces);
|
|
|
|
numSpaces -= spaces.length;
|
|
|
|
}
|
|
|
|
writeString(spaces[0 .. numSpaces]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Start new line.
|
2012-09-13 19:37:28 +00:00
|
|
|
void writeLineBreak(const string data = null) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
whitespace_ = indentation_ = true;
|
|
|
|
++line_;
|
|
|
|
column_ = 0;
|
|
|
|
writeString(data is null ? lineBreak(bestLineBreak_) : data);
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write a YAML version directive.
|
2012-09-13 19:37:28 +00:00
|
|
|
void writeVersionDirective(const string versionText) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeString("%YAML ");
|
|
|
|
writeString(versionText);
|
|
|
|
writeLineBreak();
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write a tag directive.
|
2012-09-13 19:37:28 +00:00
|
|
|
void writeTagDirective(const string handle, const string prefix) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeString("%TAG ");
|
|
|
|
writeString(handle);
|
|
|
|
writeString(" ");
|
|
|
|
writeString(prefix);
|
|
|
|
writeLineBreak();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
///RAII struct used to write out scalar values.
|
|
|
|
struct ScalarWriter
|
|
|
|
{
|
|
|
|
invariant()
|
|
|
|
{
|
|
|
|
assert(emitter_.bestIndent_ > 0 && emitter_.bestIndent_ < 10,
|
|
|
|
"Emitter bestIndent must be 1 to 9 for one-character indent hint");
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2011-10-30 19:24:43 +00:00
|
|
|
@disable int opCmp(ref Emitter);
|
|
|
|
@disable bool opEquals(ref Emitter);
|
|
|
|
|
2011-10-11 13:58:23 +00:00
|
|
|
///Used as "null" UTF-32 character.
|
2013-05-30 22:23:23 +00:00
|
|
|
static immutable dcharNone = dchar.max;
|
2011-10-11 13:58:23 +00:00
|
|
|
|
|
|
|
///Emitter used to emit the scalar.
|
|
|
|
Emitter* emitter_;
|
|
|
|
|
|
|
|
///UTF-8 encoded text of the scalar to write.
|
|
|
|
string text_;
|
|
|
|
|
|
|
|
///Can we split the scalar into multiple lines?
|
|
|
|
bool split_;
|
|
|
|
///Are we currently going over spaces in the text?
|
|
|
|
bool spaces_;
|
|
|
|
///Are we currently going over line breaks in the text?
|
|
|
|
bool breaks_;
|
|
|
|
|
|
|
|
///Start and end byte of the text range we're currently working with.
|
|
|
|
size_t startByte_, endByte_;
|
|
|
|
///End byte of the text range including the currently processed character.
|
|
|
|
size_t nextEndByte_;
|
|
|
|
///Start and end character of the text range we're currently working with.
|
|
|
|
long startChar_, endChar_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
///Construct a ScalarWriter using emitter to output text.
|
2012-09-13 23:16:05 +00:00
|
|
|
this(ref Emitter emitter, string text, const bool split = true) @trusted nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
emitter_ = &emitter;
|
|
|
|
text_ = text;
|
|
|
|
split_ = split;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Destroy the ScalarWriter.
|
2012-09-08 23:42:13 +00:00
|
|
|
@trusted nothrow ~this()
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
text_ = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write text as single quoted scalar.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeSingleQuoted() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
emitter_.writeIndicator("\'", Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
spaces_ = breaks_ = false;
|
|
|
|
resetTextPosition();
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
const dchar c = nextChar();
|
|
|
|
if(spaces_)
|
|
|
|
{
|
|
|
|
if(c != ' ' && tooWide() && split_ &&
|
|
|
|
startByte_ != 0 && endByte_ != text_.length)
|
|
|
|
{
|
|
|
|
writeIndent(Flag!"ResetSpace".no);
|
|
|
|
updateRangeStart();
|
|
|
|
}
|
|
|
|
else if(c != ' ')
|
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".yes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(breaks_)
|
|
|
|
{
|
2011-10-25 18:23:44 +00:00
|
|
|
if(!newlineSearch_.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeStartLineBreak();
|
|
|
|
writeLineBreaks();
|
|
|
|
emitter_.writeIndent();
|
|
|
|
}
|
|
|
|
}
|
2011-10-26 04:30:10 +00:00
|
|
|
else if((c == dcharNone || c == '\'' || c == ' ' || newlineSearch_.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
&& startChar_ < endChar_)
|
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".yes);
|
|
|
|
}
|
|
|
|
if(c == '\'')
|
|
|
|
{
|
|
|
|
emitter_.column_ += 2;
|
|
|
|
emitter_.writeString("\'\'");
|
|
|
|
startByte_ = endByte_ + 1;
|
|
|
|
startChar_ = endChar_ + 1;
|
|
|
|
}
|
|
|
|
updateBreaks(c, Flag!"UpdateSpaces".yes);
|
2011-10-28 22:31:14 +00:00
|
|
|
}while(endByte_ < text_.length);
|
2011-10-26 04:30:10 +00:00
|
|
|
|
2012-09-13 23:16:05 +00:00
|
|
|
emitter_.writeIndicator("\'", No.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Write text as double quoted scalar.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeDoubleQuoted() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
resetTextPosition();
|
2012-09-13 23:16:05 +00:00
|
|
|
emitter_.writeIndicator("\"", Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
do
|
|
|
|
{
|
|
|
|
const dchar c = nextChar();
|
|
|
|
//handle special characters
|
2011-10-23 22:46:35 +00:00
|
|
|
if(c == dcharNone || "\"\\\u0085\u2028\u2029\uFEFF"d.canFind(c) ||
|
2011-10-11 13:58:23 +00:00
|
|
|
!((c >= '\x20' && c <= '\x7E') ||
|
|
|
|
((c >= '\xA0' && c <= '\uD7FF') || (c >= '\uE000' && c <= '\uFFFD'))))
|
|
|
|
{
|
|
|
|
if(startChar_ < endChar_)
|
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".yes);
|
|
|
|
}
|
|
|
|
if(c != dcharNone)
|
|
|
|
{
|
|
|
|
auto appender = appender!string();
|
2011-10-23 18:17:37 +00:00
|
|
|
if((c in dyaml.escapes.toEscapes) !is null)
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
appender.put('\\');
|
2011-10-23 18:17:37 +00:00
|
|
|
appender.put(dyaml.escapes.toEscapes[c]);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Write an escaped Unicode character.
|
|
|
|
const format = c <= 255 ? "\\x%02X":
|
2015-09-25 06:18:37 +00:00
|
|
|
c <= 65535 ? "\\u%04X": "\\U%08X";
|
2011-10-11 13:58:23 +00:00
|
|
|
formattedWrite(appender, format, cast(uint)c);
|
|
|
|
}
|
|
|
|
|
|
|
|
emitter_.column_ += appender.data.length;
|
|
|
|
emitter_.writeString(appender.data);
|
|
|
|
startChar_ = endChar_ + 1;
|
|
|
|
startByte_ = nextEndByte_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if((endByte_ > 0 && endByte_ < text_.length - strideBack(text_, text_.length))
|
|
|
|
&& (c == ' ' || startChar_ >= endChar_)
|
|
|
|
&& (emitter_.column_ + endChar_ - startChar_ > emitter_.bestWidth_)
|
|
|
|
&& split_)
|
|
|
|
{
|
|
|
|
//text_[2:1] is ok in Python but not in D, so we have to use min()
|
|
|
|
emitter_.writeString(text_[min(startByte_, endByte_) .. endByte_]);
|
|
|
|
emitter_.writeString("\\");
|
|
|
|
emitter_.column_ += startChar_ - endChar_ + 1;
|
|
|
|
startChar_ = max(startChar_, endChar_);
|
|
|
|
startByte_ = max(startByte_, endByte_);
|
|
|
|
|
|
|
|
writeIndent(Flag!"ResetSpace".yes);
|
|
|
|
if(charAtStart() == ' ')
|
|
|
|
{
|
|
|
|
emitter_.writeString("\\");
|
|
|
|
++emitter_.column_;
|
|
|
|
}
|
|
|
|
}
|
2011-10-28 22:31:14 +00:00
|
|
|
}while(endByte_ < text_.length);
|
2012-09-13 23:16:05 +00:00
|
|
|
emitter_.writeIndicator("\"", No.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Write text as folded block scalar.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeFolded() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
initBlock('>');
|
|
|
|
bool leadingSpace = true;
|
|
|
|
spaces_ = false;
|
|
|
|
breaks_ = true;
|
|
|
|
resetTextPosition();
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
const dchar c = nextChar();
|
|
|
|
if(breaks_)
|
|
|
|
{
|
2011-10-25 18:23:44 +00:00
|
|
|
if(!newlineSearch_.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(!leadingSpace && c != dcharNone && c != ' ')
|
|
|
|
{
|
|
|
|
writeStartLineBreak();
|
|
|
|
}
|
|
|
|
leadingSpace = (c == ' ');
|
|
|
|
writeLineBreaks();
|
|
|
|
if(c != dcharNone){emitter_.writeIndent();}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(spaces_)
|
|
|
|
{
|
|
|
|
if(c != ' ' && tooWide())
|
|
|
|
{
|
|
|
|
writeIndent(Flag!"ResetSpace".no);
|
|
|
|
updateRangeStart();
|
|
|
|
}
|
|
|
|
else if(c != ' ')
|
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".yes);
|
|
|
|
}
|
|
|
|
}
|
2011-10-25 18:23:44 +00:00
|
|
|
else if(c == dcharNone || newlineSearch_.canFind(c) || c == ' ')
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".yes);
|
|
|
|
if(c == dcharNone){emitter_.writeLineBreak();}
|
|
|
|
}
|
|
|
|
updateBreaks(c, Flag!"UpdateSpaces".yes);
|
2011-10-28 22:31:14 +00:00
|
|
|
}while(endByte_ < text_.length);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Write text as literal block scalar.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeLiteral() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
initBlock('|');
|
|
|
|
breaks_ = true;
|
|
|
|
resetTextPosition();
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
const dchar c = nextChar();
|
|
|
|
if(breaks_)
|
|
|
|
{
|
2011-10-25 18:23:44 +00:00
|
|
|
if(!newlineSearch_.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeLineBreaks();
|
|
|
|
if(c != dcharNone){emitter_.writeIndent();}
|
|
|
|
}
|
|
|
|
}
|
2011-10-25 18:23:44 +00:00
|
|
|
else if(c == dcharNone || newlineSearch_.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".no);
|
|
|
|
if(c == dcharNone){emitter_.writeLineBreak();}
|
|
|
|
}
|
|
|
|
updateBreaks(c, Flag!"UpdateSpaces".no);
|
2011-10-28 22:31:14 +00:00
|
|
|
}while(endByte_ < text_.length);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Write text as plain scalar.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writePlain() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2012-09-13 23:16:05 +00:00
|
|
|
if(emitter_.context_ == Emitter.Context.Root){emitter_.openEnded_ = true;}
|
2011-10-11 13:58:23 +00:00
|
|
|
if(text_ == ""){return;}
|
|
|
|
if(!emitter_.whitespace_)
|
|
|
|
{
|
|
|
|
++emitter_.column_;
|
|
|
|
emitter_.writeString(" ");
|
|
|
|
}
|
|
|
|
emitter_.whitespace_ = emitter_.indentation_ = false;
|
|
|
|
spaces_ = breaks_ = false;
|
|
|
|
resetTextPosition();
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
const dchar c = nextChar();
|
|
|
|
if(spaces_)
|
|
|
|
{
|
|
|
|
if(c != ' ' && tooWide() && split_)
|
|
|
|
{
|
|
|
|
writeIndent(Flag!"ResetSpace".yes);
|
|
|
|
updateRangeStart();
|
|
|
|
}
|
|
|
|
else if(c != ' ')
|
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".yes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(breaks_)
|
|
|
|
{
|
2011-10-25 18:23:44 +00:00
|
|
|
if(!newlineSearch_.canFind(c))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeStartLineBreak();
|
|
|
|
writeLineBreaks();
|
|
|
|
writeIndent(Flag!"ResetSpace".yes);
|
|
|
|
}
|
|
|
|
}
|
2011-10-25 18:23:44 +00:00
|
|
|
else if(c == dcharNone || newlineSearch_.canFind(c) || c == ' ')
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
writeCurrentRange(Flag!"UpdateColumn".yes);
|
|
|
|
}
|
|
|
|
updateBreaks(c, Flag!"UpdateSpaces".yes);
|
2011-10-28 22:31:14 +00:00
|
|
|
}while(endByte_ < text_.length);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
///Get next character and move end of the text range to it.
|
2013-04-23 23:32:16 +00:00
|
|
|
@property dchar nextChar() pure @safe
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
++endChar_;
|
|
|
|
endByte_ = nextEndByte_;
|
2011-10-25 18:23:44 +00:00
|
|
|
if(endByte_ >= text_.length){return dcharNone;}
|
|
|
|
const c = text_[nextEndByte_];
|
|
|
|
//c is ascii, no need to decode.
|
|
|
|
if(c < 0x80)
|
|
|
|
{
|
|
|
|
++nextEndByte_;
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
return decode(text_, nextEndByte_);
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///Get character at start of the text range.
|
2013-04-23 23:32:16 +00:00
|
|
|
@property dchar charAtStart() const pure @safe
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
size_t idx = startByte_;
|
|
|
|
return decode(text_, idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
///Is the current line too wide?
|
2013-04-23 23:32:16 +00:00
|
|
|
@property bool tooWide() const pure @safe nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
return startChar_ + 1 == endChar_ &&
|
|
|
|
emitter_.column_ > emitter_.bestWidth_;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Determine hints (indicators) for block scalar.
|
2013-03-23 16:25:52 +00:00
|
|
|
size_t determineBlockHints(char[] hints, uint bestIndent) const pure @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
size_t hintsIdx = 0;
|
|
|
|
if(text_.length == 0){return hintsIdx;}
|
|
|
|
|
2012-09-13 19:37:28 +00:00
|
|
|
dchar lastChar(const string str, ref size_t end)
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
size_t idx = end = end - strideBack(str, end);
|
|
|
|
return decode(text_, idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t end = text_.length;
|
|
|
|
const last = lastChar(text_, end);
|
|
|
|
const secondLast = end > 0 ? lastChar(text_, end) : 0;
|
|
|
|
|
2011-10-25 18:23:44 +00:00
|
|
|
if(newlineSearch_.canFind(text_[0]) || text_[0] == ' ')
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
hints[hintsIdx++] = cast(char)('0' + bestIndent);
|
|
|
|
}
|
2011-10-25 18:23:44 +00:00
|
|
|
if(!newlineSearch_.canFind(last))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
hints[hintsIdx++] = '-';
|
|
|
|
}
|
2011-10-25 18:23:44 +00:00
|
|
|
else if(std.utf.count(text_) == 1 || newlineSearch_.canFind(secondLast))
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
hints[hintsIdx++] = '+';
|
|
|
|
}
|
|
|
|
return hintsIdx;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Initialize for block scalar writing with specified indicator.
|
2012-09-13 23:16:05 +00:00
|
|
|
void initBlock(const char indicator) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
char[4] hints;
|
|
|
|
hints[0] = indicator;
|
2011-10-30 19:24:43 +00:00
|
|
|
const hintsLength = 1 + determineBlockHints(hints[1 .. $], emitter_.bestIndent_);
|
2012-09-13 23:16:05 +00:00
|
|
|
emitter_.writeIndicator(cast(string)hints[0 .. hintsLength], Yes.needWhitespace);
|
2011-10-11 13:58:23 +00:00
|
|
|
if(hints.length > 0 && hints[$ - 1] == '+')
|
|
|
|
{
|
|
|
|
emitter_.openEnded_ = true;
|
|
|
|
}
|
|
|
|
emitter_.writeLineBreak();
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write out the current text range.
|
2012-09-13 23:16:05 +00:00
|
|
|
void writeCurrentRange(const Flag!"UpdateColumn" updateColumn) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
emitter_.writeString(text_[startByte_ .. endByte_]);
|
|
|
|
if(updateColumn){emitter_.column_ += endChar_ - startChar_;}
|
|
|
|
updateRangeStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write line breaks in the text range.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeLineBreaks() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
foreach(const dchar br; text_[startByte_ .. endByte_])
|
|
|
|
{
|
|
|
|
if(br == '\n'){emitter_.writeLineBreak();}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char[4] brString;
|
|
|
|
const bytes = encode(brString, br);
|
|
|
|
emitter_.writeLineBreak(cast(string)brString[0 .. bytes]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateRangeStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write line break if start of the text range is a newline.
|
2012-09-08 23:42:13 +00:00
|
|
|
void writeStartLineBreak() @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(charAtStart == '\n'){emitter_.writeLineBreak();}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Write indentation, optionally resetting whitespace/indentation flags.
|
2012-09-13 23:16:05 +00:00
|
|
|
void writeIndent(const Flag!"ResetSpace" resetSpace) @system
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
emitter_.writeIndent();
|
|
|
|
if(resetSpace)
|
|
|
|
{
|
|
|
|
emitter_.whitespace_ = emitter_.indentation_ = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Move start of text range to its end.
|
2012-09-08 23:42:13 +00:00
|
|
|
void updateRangeStart() pure @safe nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
startByte_ = endByte_;
|
|
|
|
startChar_ = endChar_;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Update the line breaks_ flag, optionally updating the spaces_ flag.
|
2012-09-13 23:16:05 +00:00
|
|
|
void updateBreaks(in dchar c, const Flag!"UpdateSpaces" updateSpaces) pure @trusted
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
if(c == dcharNone){return;}
|
2011-10-25 18:23:44 +00:00
|
|
|
breaks_ = newlineSearch_.canFind(c);
|
2011-10-11 13:58:23 +00:00
|
|
|
if(updateSpaces){spaces_ = c == ' ';}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Move to the beginning of text.
|
2012-09-08 23:42:13 +00:00
|
|
|
void resetTextPosition() pure @safe nothrow
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
|
|
|
startByte_ = endByte_ = nextEndByte_ = 0;
|
|
|
|
startChar_ = endChar_ = -1;
|
|
|
|
}
|
|
|
|
}
|