package yaml; import Type; import yaml.util.ObjectMap; import yaml.util.StringMap; import yaml.util.IntMap; import haxe.Utf8; import haxe.PosInfos; import yaml.schema.DefaultSchema; import yaml.schema.SafeSchema; import yaml.YamlType; import yaml.util.Strings; import yaml.util.Ints; class ParserOptions { public var schema:Schema; public var resolve:Bool; public var validation:Bool; public var strict:Bool; public var maps:Bool; /** @param schema Defines the schema to use while parsing. Defaults to yaml.schema.DefaultSchema. */ public function new(?schema:Schema = null) { this.schema = (schema == null) ? new DefaultSchema() : schema; strict = false; resolve = true; validation = true; maps = true; } /** Use yaml.util.ObjectMap as the key => value container. Allows for complex key values. */ public function useMaps():ParserOptions { maps = true; return this; } /** Use Dynamic objects as the key => value container. All keys will become Strings. */ public function useObjects():ParserOptions { maps = false; return this; } /** Defines the schema to use while parsing. See yaml.Schema */ public function setSchema(schema:Schema):ParserOptions { this.schema = schema; return this; } /** Warnings will be thrown as exceptions instead of traced. */ public function strictMode(?value:Bool = true):ParserOptions { strict = value; return this; } /** Disables validation of yaml document while parsing. */ public function validate(?value:Bool = true):ParserOptions { validation = value; return this; } } class Parser { /** Utility method to create ParserOptions for configuring a Parser instance. */ public static function options():ParserOptions { return new ParserOptions(); } var schema:Schema; var resolve:Bool; var validate:Bool; var strict:Bool; var usingMaps:Bool; var directiveHandlers:StringMapArray->Void>; var implicitTypes:Array; var typeMap:StringMap; var length:Int; var position:Int; var line:Int; var lineStart:Int; var lineIndent:Int; var character:Null; var version:String; var checkLineBreaks:Bool; var tagMap:StringMap; var anchorMap:StringMap; var tag:String; var anchor:String; var kind:String; var result:Dynamic; var input:String; var output:Dynamic->Void; public function new() {} public function safeParseAll(input:String, output:Dynamic->Void, options:ParserOptions):Void { options.schema = new SafeSchema(); parseAll(input, output, options); } public function safeParse(input:String, options:ParserOptions) { options.schema = new SafeSchema(); return parse(input, options); } public function parse(input:String, options:ParserOptions):Dynamic { var result:Dynamic = null; var received = false; var responder = function (data:Dynamic) { if (!received) { result = data; received = true; } else { throw new YamlException('expected a single document in the stream, but found more'); } } parseAll(input, responder, options); return result; } public function parseAll(input:String, output:Dynamic->Void, options:ParserOptions):Void { #if (neko || cpp) this.input = Utf8.encode(input); #else this.input = input; #end this.output = output; schema = options.schema; resolve = options.resolve; validate = options.validation; strict = options.strict; usingMaps = options.maps; directiveHandlers = new StringMap(); implicitTypes = schema.compiledImplicit; typeMap = schema.compiledTypeMap; length = Utf8.length(this.input);//.length; position = 0; line = 0; lineStart = 0; lineIndent = 0; character = Utf8.charCodeAt(this.input, position); directiveHandlers.set('YAML', function(name:String, args:Array) { #if (cpp || neko) for (i in 0...args.length) args[i] = Utf8.encode(args[i]); #end if (null != version) throwError('duplication of %YAML directive'); if (1 != args.length) throwError('YAML directive accepts exactly one argument'); // match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); var regex = ~/^([0-9]+)\.([0-9]+)$/u; if (!regex.match(args[0])) throwError('ill-formed argument of the YAML directive'); var major = Ints.parseInt(regex.matched(1), 10); var minor = Ints.parseInt(regex.matched(2), 10); if (1 != major) throwError('unacceptable YAML version of the document'); version = args[0]; checkLineBreaks = (minor < 2); if (1 != minor && 2 != minor) throwWarning('unsupported YAML version of the document'); }); directiveHandlers.set('TAG', function(name:String, args:Array) { #if (cpp || neko) for (i in 0...args.length) args[i] = Utf8.encode(args[i]); #end var handle:String; var prefix:String; if (2 != args.length) throwError('TAG directive accepts exactly two arguments'); handle = args[0]; prefix = args[1]; if (!PATTERN_TAG_HANDLE.match(handle)) throwError('ill-formed tag handle (first argument) of the TAG directive'); if (tagMap.exists(handle)) throwError('there is a previously declared suffix for "' + handle + '" tag handle'); if (!PATTERN_TAG_URI.match(prefix)) throwError('ill-formed tag prefix (second argument) of the TAG directive'); tagMap.set(handle, prefix); }); if (validate && PATTERN_NON_PRINTABLE.match(this.input)) { throwError('the stream contains non-printable characters'); } while (CHAR_SPACE == character) { lineIndent += 1; character = Utf8.charCodeAt(input, ++position); } while (position < length) { readDocument(); } } function generateError(message:String, ?info:PosInfos) { return new YamlException(message, info); } function throwError(message:String, ?info:PosInfos) { throw generateError(message, info); } function throwWarning(message:String, ?info:PosInfos) { var error = generateError(message, info); if (strict) { throw error; } else { trace("Warning : " + error.toString()); } } function captureSegment(start:Int, end:Int, checkJson:Bool) { var _result:String; if (start < end) { _result = yaml.util.Utf8.substring(input, start, end); if (checkJson && validate) { for (pos in 0...Utf8.length(_result))//.length) { var char = Utf8.charCodeAt(_result, pos); if (!(0x09 == char || 0x20 <= char && char <= 0x10FFFF)) throwError('expected valid JSON character'); } } result += _result; } } // when create dynamic object graph function mergeObjectMappings(destination:Dynamic, source:Dynamic) { if (Type.typeof(source) != ValueType.TObject) { throwError('cannot merge mappings; the provided source object is unacceptable'); } for (key in Reflect.fields(source)) if (!Reflect.hasField(destination, key)) Reflect.setField(destination, key, Reflect.field(source, key)); } // when creating map based graph function mergeMappings(destination:AnyObjectMap, source:AnyObjectMap) { if (!Std.is(source, AnyObjectMap)) { throwError('cannot merge mappings; the provided source object is unacceptable'); } for (key in source.keys()) if (!destination.exists(key)) destination.set(key, source.get(key)); } function storeObjectMappingPair(_result:Dynamic, keyTag:String, keyNode:Dynamic, valueNode:Dynamic):Dynamic { if (null == _result) _result = {}; if ('tag:yaml.org,2002:merge' == keyTag) { if (Std.is(valueNode, Array)) { var list:Array = cast valueNode; for (member in list) mergeObjectMappings(_result, member); } else { mergeObjectMappings(_result, valueNode); } } else { Reflect.setField(_result, Std.string(keyNode), valueNode); } return _result; } function storeMappingPair(_result:AnyObjectMap, keyTag:String, keyNode:Dynamic, valueNode:Dynamic):AnyObjectMap { if (null == _result) { _result = new AnyObjectMap(); } if ('tag:yaml.org,2002:merge' == keyTag) { if (Std.is(valueNode, Array)) { var list:Array = cast valueNode; for (member in list) mergeMappings(_result, member); } else { mergeMappings(_result, valueNode); } } else { _result.set(keyNode, valueNode); } return _result; } function readLineBreak() { if (CHAR_LINE_FEED == character) { position += 1; } else if (CHAR_CARRIAGE_RETURN == character) { if (CHAR_LINE_FEED == Utf8.charCodeAt(input, (position + 1))) { position += 2; } else { position += 1; } } else { throwError('a line break is expected'); } line += 1; lineStart = position; if (position < length) character = Utf8.charCodeAt(input, position); else character = null; } function skipSeparationSpace(allowComments:Bool, checkIndent:Int) { var lineBreaks = 0; while (position < length) { while (CHAR_SPACE == character || CHAR_TAB == character) { character = Utf8.charCodeAt(input, ++position); } if (allowComments && CHAR_SHARP == character) { do { character = Utf8.charCodeAt(input, ++position); } while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character); } if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character) { readLineBreak(); lineBreaks += 1; lineIndent = 0; while (CHAR_SPACE == character) { lineIndent += 1; character = Utf8.charCodeAt(input, ++position); } if (lineIndent < checkIndent) { throwWarning('deficient indentation'); } } else { break; } } return lineBreaks; } function testDocumentSeparator() { if (position == lineStart && (CHAR_MINUS == character || CHAR_DOT == character) && Utf8.charCodeAt(input, (position + 1)) == character && Utf8.charCodeAt(input, (position + 2)) == character) { var pos = position + 3; var char = Utf8.charCodeAt(input, pos); if (pos >= length || CHAR_SPACE == char || CHAR_TAB == char || CHAR_LINE_FEED == char || CHAR_CARRIAGE_RETURN == char) { return true; } } return false; } function writeFoldedLines(count:Int) { if (1 == count) { result += ' '; } else if (count > 1) { #if (cpp || neko) result += Utf8.encode(Strings.repeat('\n', count - 1)); #else result += Strings.repeat('\n', count - 1); #end } } function readPlainScalar(nodeIndent:Int, withinFlowCollection:Bool) { var preceding:Int; var following:Int; var captureStart:Int; var captureEnd:Int; var hasPendingContent; var _line:Int = 0; var _kind = kind; var _result = result; if (CHAR_SPACE == character || CHAR_TAB == character || CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character || CHAR_COMMA == character || CHAR_LEFT_SQUARE_BRACKET == character || CHAR_RIGHT_SQUARE_BRACKET == character || CHAR_LEFT_CURLY_BRACKET == character || CHAR_RIGHT_CURLY_BRACKET == character || CHAR_SHARP == character || CHAR_AMPERSAND == character || CHAR_ASTERISK == character || CHAR_EXCLAMATION == character || CHAR_VERTICAL_LINE == character || CHAR_GREATER_THAN == character || CHAR_SINGLE_QUOTE == character || CHAR_DOUBLE_QUOTE == character || CHAR_PERCENT == character || CHAR_COMMERCIAL_AT == character || CHAR_GRAVE_ACCENT == character) { return false; } if (CHAR_QUESTION == character || CHAR_MINUS == character) { following = Utf8.charCodeAt(input, position + 1); if (CHAR_SPACE == following || CHAR_TAB == following || CHAR_LINE_FEED == following || CHAR_CARRIAGE_RETURN == following || withinFlowCollection && (CHAR_COMMA == following || CHAR_LEFT_SQUARE_BRACKET == following || CHAR_RIGHT_SQUARE_BRACKET == following || CHAR_LEFT_CURLY_BRACKET == following || CHAR_RIGHT_CURLY_BRACKET == following)) { return false; } } kind = KIND_STRING; result = ''; captureStart = captureEnd = position; hasPendingContent = false; while (position < length) { if (CHAR_COLON == character) { following = Utf8.charCodeAt(input, position + 1); if (CHAR_SPACE == following || CHAR_TAB == following || CHAR_LINE_FEED == following || CHAR_CARRIAGE_RETURN == following || withinFlowCollection && (CHAR_COMMA == following || CHAR_LEFT_SQUARE_BRACKET == following || CHAR_RIGHT_SQUARE_BRACKET == following || CHAR_LEFT_CURLY_BRACKET == following || CHAR_RIGHT_CURLY_BRACKET == following)) { break; } } else if (CHAR_SHARP == character) { preceding = Utf8.charCodeAt(input, position - 1); if (CHAR_SPACE == preceding || CHAR_TAB == preceding || CHAR_LINE_FEED == preceding || CHAR_CARRIAGE_RETURN == preceding) { break; } } else if ((position == lineStart && testDocumentSeparator()) || withinFlowCollection && (CHAR_COMMA == character || CHAR_LEFT_SQUARE_BRACKET == character || CHAR_RIGHT_SQUARE_BRACKET == character || CHAR_LEFT_CURLY_BRACKET == character || CHAR_RIGHT_CURLY_BRACKET == character)) { break; } else if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character) { _line = line; var _lineStart = lineStart; var _lineIndent = lineIndent; skipSeparationSpace(false, -1); if (lineIndent >= nodeIndent) { hasPendingContent = true; continue; } else { position = captureEnd; line = _line; lineStart = _lineStart; lineIndent = _lineIndent; character = Utf8.charCodeAt(input, position); break; } } if (hasPendingContent) { captureSegment(captureStart, captureEnd, false); writeFoldedLines(line - _line); captureStart = captureEnd = position; hasPendingContent = false; } if (CHAR_SPACE != character && CHAR_TAB != character) { captureEnd = position + 1; } if (++position >= length) break; character = Utf8.charCodeAt(input, position); } captureSegment(captureStart, captureEnd, false); if (result != null) { #if sys result = Utf8.decode(result); // convert back into native encoding #end return true; } else { kind = _kind; result = _result; return false; } } function readSingleQuotedScalar(nodeIndent:Int) { var captureStart:Int; var captureEnd:Int; if (CHAR_SINGLE_QUOTE != character) { return false; } kind = KIND_STRING; result = ''; character = Utf8.charCodeAt(input, ++position); captureStart = captureEnd = position; while (position < length) { if (CHAR_SINGLE_QUOTE == character) { captureSegment(captureStart, position, true); character = Utf8.charCodeAt(input, ++position); if (CHAR_SINGLE_QUOTE == character) { captureStart = captureEnd = position; character = Utf8.charCodeAt(input, ++position); } else { #if sys result = Utf8.decode(result); #end return true; } } else if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character) { captureSegment(captureStart, captureEnd, true); writeFoldedLines(skipSeparationSpace(false, nodeIndent)); captureStart = captureEnd = position; character = Utf8.charCodeAt(input, position); } else if (position == lineStart && testDocumentSeparator()) { throwError('unexpected end of the document within a single quoted scalar'); } else { character = Utf8.charCodeAt(input, ++position); captureEnd = position; } } throwError('unexpected end of the stream within a single quoted scalar'); return false; } function readDoubleQuotedScalar(nodeIndent:Int) { var captureStart:Int; var captureEnd:Int; if (CHAR_DOUBLE_QUOTE != character) return false; kind = KIND_STRING; result = ''; character = Utf8.charCodeAt(input, ++position); captureStart = captureEnd = position; while (position < length) { if (CHAR_DOUBLE_QUOTE == character) { captureSegment(captureStart, position, true); character = Utf8.charCodeAt(input, ++position); #if sys result = Utf8.decode(result); #end return true; } else if (CHAR_BACKSLASH == character) { captureSegment(captureStart, position, true); character = Utf8.charCodeAt(input, ++position); if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character) { skipSeparationSpace(false, nodeIndent); } else if (SIMPLE_ESCAPE_SEQUENCES.exists(character)) { result += SIMPLE_ESCAPE_SEQUENCES.get(character); character = Utf8.charCodeAt(input, ++position); } else if (HEXADECIMAL_ESCAPE_SEQUENCES.exists(character)) { var hexLength = HEXADECIMAL_ESCAPE_SEQUENCES.get(character); var hexResult = 0; for (hexIndex in 1...hexLength) { var hexOffset = (hexLength - hexIndex) * 4; character = Utf8.charCodeAt(input, ++position); if (CHAR_DIGIT_ZERO <= character && character <= CHAR_DIGIT_NINE) { hexResult |= (character - CHAR_DIGIT_ZERO) << hexOffset; } else if (CHAR_CAPITAL_A <= character && character <= CHAR_CAPITAL_F) { hexResult |= (character - CHAR_CAPITAL_A + 10) << hexOffset; } else if (CHAR_SMALL_A <= character && character <= CHAR_SMALL_F) { hexResult |= (character - CHAR_SMALL_A + 10) << hexOffset; } else { throwError('expected hexadecimal character'); } } result += String.fromCharCode(hexResult); character = Utf8.charCodeAt(input, ++position); } else { throwError('unknown escape sequence'); } captureStart = captureEnd = position; } else if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character) { captureSegment(captureStart, captureEnd, true); writeFoldedLines(skipSeparationSpace(false, nodeIndent)); captureStart = captureEnd = position; character = Utf8.charCodeAt(input, position); } else if (position == lineStart && testDocumentSeparator()) { throwError('unexpected end of the document within a double quoted scalar'); } else { character = Utf8.charCodeAt(input, ++position); captureEnd = position; } } throwError('unexpected end of the stream within a double quoted scalar'); return false; } function composeNode(parentIndent:Int, nodeContext:Int, allowToSeek:Bool, allowCompact:Bool) { var allowBlockStyles:Bool; var allowBlockScalars:Bool; var allowBlockCollections:Bool; var atNewLine = false; var isIndented = true; var hasContent = false; tag = null; anchor = null; kind = null; result = null; allowBlockCollections = (CONTEXT_BLOCK_OUT == nodeContext || CONTEXT_BLOCK_IN == nodeContext); allowBlockStyles = allowBlockScalars = allowBlockCollections; if (allowToSeek) { if (skipSeparationSpace(true, -1) != 0) { atNewLine = true; if (lineIndent == parentIndent) { isIndented = false; } else if (lineIndent > parentIndent) { isIndented = true; } else { return false; } } } if (isIndented) { while (readTagProperty() || readAnchorProperty()) { if (skipSeparationSpace(true, -1) != 0) { atNewLine = true; if (lineIndent > parentIndent) { isIndented = true; allowBlockCollections = allowBlockStyles; } else if (lineIndent == parentIndent) { isIndented = false; allowBlockCollections = allowBlockStyles; } else { return true; } } else { allowBlockCollections = false; } } } if (allowBlockCollections) allowBlockCollections = atNewLine || allowCompact; if (isIndented || CONTEXT_BLOCK_OUT == nodeContext) { var flowIndent:Int; var blockIndent:Int; if (CONTEXT_FLOW_IN == nodeContext || CONTEXT_FLOW_OUT == nodeContext) { flowIndent = parentIndent; } else { flowIndent = parentIndent + 1; } blockIndent = position - lineStart; if (isIndented) { if (allowBlockCollections && (readBlockSequence(blockIndent) || readBlockMapping(blockIndent)) || readFlowCollection(flowIndent)) { hasContent = true; } else { if ((allowBlockScalars && readBlockScalar(flowIndent)) || readSingleQuotedScalar(flowIndent) || readDoubleQuotedScalar(flowIndent)) { hasContent = true; } else if (readAlias()) { hasContent = true; if (null != tag || null != anchor) { throwError('alias node should not have any properties'); } } else if (readPlainScalar(flowIndent, CONTEXT_FLOW_IN == nodeContext)) { hasContent = true; if (null == tag) tag = '?'; } if (null != anchor) { anchorMap.set(anchor, result); } } } else { hasContent = allowBlockCollections && readBlockSequence(blockIndent); } } if (null != tag && '!' != tag) { var _result:Dynamic = null; if ('?' == tag) { if (resolve) { for (typeIndex in 0...implicitTypes.length) { var type = implicitTypes[typeIndex]; // Implicit resolving is not allowed for non-scalar types, and '?' // non-specific tag is only assigned to plain scalars. So, it isn't // needed to check for 'kind' conformity. var resolvedType = false; try { _result = type.resolve(result, usingMaps, false); #if sys if (Std.is(_result, String)) _result = Utf8.decode(_result); #end tag = type.tag; result = _result; resolvedType = true; } catch (e:ResolveTypeException) {} if (resolvedType) break; } } } else if (typeMap.exists(tag)) { var t = typeMap.get(tag); if (null != result && t.loader.kind != kind) { throwError('unacceptable node kind for !<' + tag + '> tag; it should be "' + t.loader.kind + '", not "' + kind + '"'); } if (!t.loader.skip) { try { _result = t.resolve(result, usingMaps, true); #if sys if (Std.is(_result, String)) _result = Utf8.decode(_result); #end result = _result; } catch(e:ResolveTypeException) { throwError('cannot resolve a node with !<' + tag + '> explicit tag'); } } } else { throwWarning('unknown tag !<' + tag + '>'); } } return (null != tag || null != anchor || hasContent); } function readFlowCollection(nodeIndent:Int) { var readNext = true; var _tag = tag; var _result:Dynamic; var terminator:Int; var isPair:Bool; var isExplicitPair:Bool; var isMapping:Bool; var keyNode:Dynamic; var keyTag:String; var valueNode:Dynamic; switch (character) { case CHAR_LEFT_SQUARE_BRACKET: terminator = CHAR_RIGHT_SQUARE_BRACKET; isMapping = false; _result = []; case CHAR_LEFT_CURLY_BRACKET: terminator = CHAR_RIGHT_CURLY_BRACKET; isMapping = true; _result = usingMaps ? new ObjectMap<{}, Dynamic>() : {}; default: return false; } if (null != anchor) anchorMap.set(anchor, _result); character = Utf8.charCodeAt(input, ++position); while (position < length) { skipSeparationSpace(true, nodeIndent); if (character == terminator) { character = Utf8.charCodeAt(input, ++position); tag = _tag; kind = isMapping ? KIND_OBJECT : KIND_ARRAY; result = _result; return true; } else if (!readNext) { throwError('missed comma between flow collection entries'); } keyTag = keyNode = valueNode = null; isPair = isExplicitPair = false; if (CHAR_QUESTION == character) { var following = Utf8.charCodeAt(input, position + 1); if (CHAR_SPACE == following || CHAR_TAB == following || CHAR_LINE_FEED == following || CHAR_CARRIAGE_RETURN == following) { isPair = isExplicitPair = true; position += 1; character = following; skipSeparationSpace(true, nodeIndent); } } var _line = line; composeNode(nodeIndent, CONTEXT_FLOW_IN, false, true); keyTag = tag; keyNode = result; if ((isExplicitPair || line == _line) && CHAR_COLON == character) { isPair = true; character = Utf8.charCodeAt(input, ++position); skipSeparationSpace(true, nodeIndent); composeNode(nodeIndent, CONTEXT_FLOW_IN, false, true); valueNode = result; } if (isMapping) { if (usingMaps) storeMappingPair(_result, keyTag, keyNode, valueNode); else storeObjectMappingPair(_result, keyTag, keyNode, valueNode); } else if (isPair) { if (usingMaps) _result.push(storeMappingPair(null, keyTag, keyNode, valueNode)); else _result.push(storeObjectMappingPair(null, keyTag, keyNode, valueNode)); } else { _result.push(keyNode); } skipSeparationSpace(true, nodeIndent); if (CHAR_COMMA == character) { readNext = true; character = Utf8.charCodeAt(input, ++position); } else { readNext = false; } } throwError('unexpected end of the stream within a flow collection'); return false; } function readBlockScalar(nodeIndent:Int) { var captureStart:Int; var folding:Bool; var chomping = CHOMPING_CLIP; var detectedIndent = false; var textIndent = nodeIndent; var emptyLines = -1; switch (character) { case CHAR_VERTICAL_LINE: folding = false; case CHAR_GREATER_THAN: folding = true; default: return false; } kind = KIND_STRING; result = ''; while (position < length) { character = Utf8.charCodeAt(input, ++position); if (CHAR_PLUS == character || CHAR_MINUS == character) { if (CHOMPING_CLIP == chomping) { chomping = (CHAR_PLUS == character) ? CHOMPING_KEEP : CHOMPING_STRIP; } else { throwError('repeat of a chomping mode identifier'); } } else if (CHAR_DIGIT_ZERO <= character && character <= CHAR_DIGIT_NINE) { if (CHAR_DIGIT_ZERO == character) { throwError('bad explicit indentation width of a block scalar; it cannot be less than one'); } else if (!detectedIndent) { textIndent = nodeIndent + (character - CHAR_DIGIT_ONE); detectedIndent = true; } else { throwError('repeat of an indentation width identifier'); } } else { break; } } if (CHAR_SPACE == character || CHAR_TAB == character) { do { character = Utf8.charCodeAt(input, ++position); } while (CHAR_SPACE == character || CHAR_TAB == character); if (CHAR_SHARP == character) { do { character = Utf8.charCodeAt(input, ++position); } while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character); } } while (position < length) { readLineBreak(); lineIndent = 0; while ((!detectedIndent || lineIndent < textIndent) && (CHAR_SPACE == character)) { lineIndent += 1; character = Utf8.charCodeAt(input, ++position); } if (!detectedIndent && lineIndent > textIndent) { textIndent = lineIndent; } if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character) { emptyLines += 1; continue; } // End of the scalar. Perform the chomping. if (lineIndent < textIndent) { if (CHOMPING_KEEP == chomping) { #if sys result += Utf8.encode(Strings.repeat('\n', emptyLines + 1)); #else result += Strings.repeat('\n', emptyLines + 1); #end } else if (CHOMPING_CLIP == chomping) { result += '\n'; } break; } detectedIndent = true; if (folding) { if (CHAR_SPACE == character || CHAR_TAB == character) { #if sys result += Utf8.encode(Strings.repeat('\n', emptyLines + 1)); #else result += Strings.repeat('\n', emptyLines + 1); #end emptyLines = 1; } else if (0 == emptyLines) { #if sys result += Utf8.encode(' '); #else result += ' '; #end emptyLines = 0; } else { #if sys result += Utf8.encode(Strings.repeat('\n', emptyLines)); #else result += Strings.repeat('\n', emptyLines); #end emptyLines = 0; } } else { #if sys result += Utf8.encode(Strings.repeat('\n', emptyLines + 1)); #else result += Strings.repeat('\n', emptyLines + 1); #end emptyLines = 0; } captureStart = position; do { character = Utf8.charCodeAt(input, ++position); } while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character); captureSegment(captureStart, position, false); } #if sys result = Utf8.decode(result); #end return true; } function readBlockSequence(nodeIndent:Int) { var _line:Int; var _tag = tag; var _result:Array = []; var following:Int; var detected = false; if (null != anchor) anchorMap.set(anchor, _result); while (position < length) { if (CHAR_MINUS != character) break; following = Utf8.charCodeAt(input, position + 1); if (CHAR_SPACE != following && CHAR_TAB != following && CHAR_LINE_FEED != following && CHAR_CARRIAGE_RETURN != following) { break; } detected = true; position += 1; character = following; if (skipSeparationSpace(true, -1) != 0) { if (lineIndent <= nodeIndent) { _result.push(null); continue; } } _line = line; composeNode(nodeIndent, CONTEXT_BLOCK_IN, false, true); _result.push(result); skipSeparationSpace(true, -1); if ((line == _line || lineIndent > nodeIndent) && position < length) { throwError('bad indentation of a sequence entry'); } else if (lineIndent < nodeIndent) { break; } } if (detected) { tag = _tag; kind = KIND_ARRAY; result = _result; return true; } else { return false; } } function readBlockMapping(nodeIndent:Int) { var following:Int; var allowCompact = false; var _line:Int; var _tag = tag; var _result:Dynamic = usingMaps ? new ObjectMap<{}, Dynamic>() : {}; var keyTag:Dynamic = null; var keyNode:Dynamic = null; var valueNode:Dynamic = null; var atExplicitKey = false; var detected = false; if (null != anchor) anchorMap.set(anchor, _result); while (position < length) { following = Utf8.charCodeAt(input, position + 1); _line = line; // Save the current line. if ((CHAR_QUESTION == character || CHAR_COLON == character) && (CHAR_SPACE == following || CHAR_TAB == following || CHAR_LINE_FEED == following || CHAR_CARRIAGE_RETURN == following)) { if (CHAR_QUESTION == character) { if (atExplicitKey) { if (usingMaps) storeMappingPair(_result, keyTag, keyNode, null); else storeObjectMappingPair(_result, keyTag, keyNode, null); keyTag = keyNode = valueNode = null; } detected = true; atExplicitKey = true; allowCompact = true; } else if (atExplicitKey) { // i.e. CHAR_COLON == character after the explicit key. atExplicitKey = false; allowCompact = true; } else { throwError('incomplete explicit mapping pair; a key node is missed'); } position += 1; character = following; } else if (composeNode(nodeIndent, CONTEXT_FLOW_OUT, false, true)) { if (line == _line) { // TODO: Remove this cycle when the flow readers will consume // trailing whitespaces like the block readers. while (CHAR_SPACE == character || CHAR_TAB == character) { character = Utf8.charCodeAt(input, ++position); } if (CHAR_COLON == character) { character = Utf8.charCodeAt(input, ++position); if (CHAR_SPACE != character && CHAR_TAB != character && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character) { throwError('a whitespace character is expected after the key-value separator within a block mapping'); } if (atExplicitKey) { if (usingMaps) storeMappingPair(_result, keyTag, keyNode, null); else storeObjectMappingPair(_result, keyTag, keyNode, null); keyTag = keyNode = valueNode = null; } detected = true; atExplicitKey = false; allowCompact = false; keyTag = tag; keyNode = result; } else if (detected) { throwError('can not read an implicit mapping pair; a colon is missed'); } else { tag = _tag; return true; // Keep the result of `composeNode`. } } else if (detected) { throwError('can not read a block mapping entry; a multiline key may not be an implicit key'); } else { tag = _tag; return true; // Keep the result of `composeNode`. } } else { break; } if (line == _line || lineIndent > nodeIndent) { if (composeNode(nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) { if (atExplicitKey) keyNode = result; else valueNode = result; } if (!atExplicitKey) { if (usingMaps) storeMappingPair(_result, keyTag, keyNode, valueNode); else storeObjectMappingPair(_result, keyTag, keyNode, valueNode); keyTag = keyNode = valueNode = null; } // TODO: It is needed only for flow node readers. It should be removed // when the flow readers will consume trailing whitespaces as well as // the block readers. skipSeparationSpace(true, -1); } if (lineIndent > nodeIndent && position < length) { throwError('bad indentation of a mapping entry'); } else if (lineIndent < nodeIndent) { break; } } if (atExplicitKey) { if (usingMaps) storeMappingPair(_result, keyTag, keyNode, null); else storeObjectMappingPair(_result, keyTag, keyNode, null); } if (detected) { tag = _tag; kind = KIND_OBJECT; result = _result; } return detected; } function readTagProperty() { var _position:Int; var isVerbatim = false; var isNamed = false; var tagHandle:String = null; var tagName:String = null; if (CHAR_EXCLAMATION != character) return false; if (null != tag) throwError('duplication of a tag property'); character = Utf8.charCodeAt(input, ++position); if (CHAR_LESS_THAN == character) { isVerbatim = true; character = Utf8.charCodeAt(input, ++position); } else if (CHAR_EXCLAMATION == character) { isNamed = true; tagHandle = '!!'; character = Utf8.charCodeAt(input, ++position); } else { tagHandle = '!'; } _position = position; if (isVerbatim) { do { character = Utf8.charCodeAt(input, ++position); } while (position < length && CHAR_GREATER_THAN != character); if (position < length) { tagName = yaml.util.Utf8.substring(input, _position, position); character = Utf8.charCodeAt(input, ++position); } else { throwError('unexpected end of the stream within a verbatim tag'); } } else { while (position < length && CHAR_SPACE != character && CHAR_TAB != character && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character) { if (CHAR_EXCLAMATION == character) { if (!isNamed) { tagHandle = yaml.util.Utf8.substring(input, _position - 1, position + 1); if (validate && !PATTERN_TAG_HANDLE.match(tagHandle)) { throwError('named tag handle cannot contain such characters'); } isNamed = true; _position = position + 1; } else { throwError('tag suffix cannot contain exclamation marks'); } } character = Utf8.charCodeAt(input, ++position); } tagName = yaml.util.Utf8.substring(input, _position, position); if (validate && PATTERN_FLOW_INDICATORS.match(tagName)) { throwError('tag suffix cannot contain flow indicator characters'); } } if (validate && tagName != null && tagName != "" && !PATTERN_TAG_URI.match(tagName)) { throwError('tag name cannot contain such characters: ' + tagName); } if (isVerbatim) { tag = tagName; } else if (tagMap.exists(tagHandle)) { tag = tagMap.get(tagHandle) + tagName; } else if ('!' == tagHandle) { tag = '!' + tagName; } else if ('!!' == tagHandle) { tag = 'tag:yaml.org,2002:' + tagName; } else { throwError('undeclared tag handle "' + tagHandle + '"'); } return true; } function readAnchorProperty() { var _position:Int; if (CHAR_AMPERSAND != character) return false; if (null != anchor) throwError('duplication of an anchor property'); character = Utf8.charCodeAt(input, ++position); _position = position; while (position < length && CHAR_SPACE != character && CHAR_TAB != character && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character && CHAR_COMMA != character && CHAR_LEFT_SQUARE_BRACKET != character && CHAR_RIGHT_SQUARE_BRACKET != character && CHAR_LEFT_CURLY_BRACKET != character && CHAR_RIGHT_CURLY_BRACKET != character) { character = Utf8.charCodeAt(input, ++position); } if (position == _position) throwError('name of an anchor node must contain at least one character'); anchor = yaml.util.Utf8.substring(input, _position, position); return true; } function readAlias() { var _position:Int; var alias:String; if (CHAR_ASTERISK != character) return false; character = Utf8.charCodeAt(input, ++position); _position = position; while (position < length && CHAR_SPACE != character && CHAR_TAB != character && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character && CHAR_COMMA != character && CHAR_LEFT_SQUARE_BRACKET != character && CHAR_RIGHT_SQUARE_BRACKET != character && CHAR_LEFT_CURLY_BRACKET != character && CHAR_RIGHT_CURLY_BRACKET != character) { character = Utf8.charCodeAt(input, ++position); } if (position == _position) throwError('name of an alias node must contain at least one character'); alias = yaml.util.Utf8.substring(input, _position, position); if (!anchorMap.exists(alias)) throwError('unidentified alias "' + alias + '"'); result = anchorMap.get(alias); skipSeparationSpace(true, -1); return true; } function readDocument() { var documentStart = position; var _position:Int; var directiveName:String; var directiveArgs:Array; var hasDirectives = false; version = null; checkLineBreaks = false; tagMap = new StringMap(); anchorMap = new StringMap(); while (position < length) { skipSeparationSpace(true, -1); if (lineIndent > 0 || CHAR_PERCENT != character) break; hasDirectives = true; character = Utf8.charCodeAt(input, ++position); _position = position; while (position < length && CHAR_SPACE != character && CHAR_TAB != character && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character) { character = Utf8.charCodeAt(input, ++position); } directiveName = yaml.util.Utf8.substring(input, _position, position); directiveArgs = []; if (Utf8.length(directiveName) < 1) throwError('directive name must not be less than one character in length'); while (position < length) { while (CHAR_SPACE == character || CHAR_TAB == character) { character = Utf8.charCodeAt(input, ++position); } if (CHAR_SHARP == character) { do { character = Utf8.charCodeAt(input, ++position); } while (position < length && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character); break; } if (CHAR_LINE_FEED == character || CHAR_CARRIAGE_RETURN == character) break; _position = position; while (position < length && CHAR_SPACE != character && CHAR_TAB != character && CHAR_LINE_FEED != character && CHAR_CARRIAGE_RETURN != character) { character = Utf8.charCodeAt(input, ++position); } directiveArgs.push(yaml.util.Utf8.substring(input, _position, position)); } if (position < length) { readLineBreak(); } if (directiveHandlers.exists(directiveName)) { directiveHandlers.get(directiveName)(directiveName, directiveArgs); } else { throwWarning('unknown document directive "' + directiveName + '"'); } } skipSeparationSpace(true, -1); if (0 == lineIndent && CHAR_MINUS == character && CHAR_MINUS == Utf8.charCodeAt(input, position + 1) && CHAR_MINUS == Utf8.charCodeAt(input, position + 2)) { position += 3; character = Utf8.charCodeAt(input, position); skipSeparationSpace(true, -1); } else if (hasDirectives) { throwError('directives end mark is expected'); } composeNode(lineIndent - 1, CONTEXT_BLOCK_OUT, false, true); skipSeparationSpace(true, -1); if (validate && checkLineBreaks && PATTERN_NON_ASCII_LINE_BREAKS.match(yaml.util.Utf8.substring(input, documentStart, position))) { throwWarning('non-ASCII line breaks are interpreted as content'); } output(result); if (position == lineStart && testDocumentSeparator()) { if (CHAR_DOT == character) { position += 3; character = Utf8.charCodeAt(input, position); skipSeparationSpace(true, -1); } return; } if (position < length) { throwError('end of the stream or a document separator is expected'); } else { return; } } public static inline var KIND_STRING = 'string'; public static inline var KIND_ARRAY = 'array'; public static inline var KIND_OBJECT = 'object'; public static inline var CONTEXT_FLOW_IN = 1; public static inline var CONTEXT_FLOW_OUT = 2; public static inline var CONTEXT_BLOCK_IN = 3; public static inline var CONTEXT_BLOCK_OUT = 4; public static inline var CHOMPING_CLIP = 1; public static inline var CHOMPING_STRIP = 2; public static inline var CHOMPING_KEEP = 3; public static inline var CHAR_TAB = 0x09; /* Tab */ public static inline var CHAR_LINE_FEED = 0x0A; /* LF */ public static inline var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */ public static inline var CHAR_SPACE = 0x20; /* Space */ public static inline var CHAR_EXCLAMATION = 0x21; /* ! */ public static inline var CHAR_DOUBLE_QUOTE = 0x22; /* " */ public static inline var CHAR_SHARP = 0x23; /* # */ public static inline var CHAR_PERCENT = 0x25; /* % */ public static inline var CHAR_AMPERSAND = 0x26; /* & */ public static inline var CHAR_SINGLE_QUOTE = 0x27; /* ' */ public static inline var CHAR_ASTERISK = 0x2A; /* * */ public static inline var CHAR_PLUS = 0x2B; /* + */ public static inline var CHAR_COMMA = 0x2C; /* , */ public static inline var CHAR_MINUS = 0x2D; /* - */ public static inline var CHAR_DOT = 0x2E; /* . */ public static inline var CHAR_SLASH = 0x2F; /* / */ public static inline var CHAR_DIGIT_ZERO = 0x30; /* 0 */ public static inline var CHAR_DIGIT_ONE = 0x31; /* 1 */ public static inline var CHAR_DIGIT_NINE = 0x39; /* 9 */ public static inline var CHAR_COLON = 0x3A; /* : */ public static inline var CHAR_LESS_THAN = 0x3C; /* < */ public static inline var CHAR_GREATER_THAN = 0x3E; /* > */ public static inline var CHAR_QUESTION = 0x3F; /* ? */ public static inline var CHAR_COMMERCIAL_AT = 0x40; /* @ */ public static inline var CHAR_CAPITAL_A = 0x41; /* A */ public static inline var CHAR_CAPITAL_F = 0x46; /* F */ public static inline var CHAR_CAPITAL_L = 0x4C; /* L */ public static inline var CHAR_CAPITAL_N = 0x4E; /* N */ public static inline var CHAR_CAPITAL_P = 0x50; /* P */ public static inline var CHAR_CAPITAL_U = 0x55; /* U */ public static inline var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */ public static inline var CHAR_BACKSLASH = 0x5C; /* \ */ public static inline var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */ public static inline var CHAR_UNDERSCORE = 0x5F; /* _ */ public static inline var CHAR_GRAVE_ACCENT = 0x60; /* ` */ public static inline var CHAR_SMALL_A = 0x61; /* a */ public static inline var CHAR_SMALL_B = 0x62; /* b */ public static inline var CHAR_SMALL_E = 0x65; /* e */ public static inline var CHAR_SMALL_F = 0x66; /* f */ public static inline var CHAR_SMALL_N = 0x6E; /* n */ public static inline var CHAR_SMALL_R = 0x72; /* r */ public static inline var CHAR_SMALL_T = 0x74; /* t */ public static inline var CHAR_SMALL_U = 0x75; /* u */ public static inline var CHAR_SMALL_V = 0x76; /* v */ public static inline var CHAR_SMALL_X = 0x78; /* x */ public static inline var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */ public static inline var CHAR_VERTICAL_LINE = 0x7C; /* | */ public static inline var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */ public static var SIMPLE_ESCAPE_SEQUENCES:IntMap = { var hash = new IntMap(); hash.set(CHAR_DIGIT_ZERO, createUtf8Char(0x00));// '\x00'); hash.set(CHAR_SMALL_A, createUtf8Char(0x07));//'\x07'); hash.set(CHAR_SMALL_B, createUtf8Char(0x08));//'\x08'); hash.set(CHAR_SMALL_T, createUtf8Char(0x09));//'\x09'); hash.set(CHAR_TAB, createUtf8Char(0x09));//'\x09'); hash.set(CHAR_SMALL_N, createUtf8Char(0x0A));//'\x0A'); hash.set(CHAR_SMALL_V, createUtf8Char(0x0B));//'\x0B'); hash.set(CHAR_SMALL_F, createUtf8Char(0x0C));//'\x0C'); hash.set(CHAR_SMALL_R, createUtf8Char(0x0D));//'\x0D'); hash.set(CHAR_SMALL_E, createUtf8Char(0x1B));//'\x1B'); hash.set(CHAR_SPACE, createUtf8Char(0x20));//' '); hash.set(CHAR_DOUBLE_QUOTE, createUtf8Char(0x22));//'\x22'); hash.set(CHAR_SLASH, createUtf8Char(0x2f));//'/'); hash.set(CHAR_BACKSLASH, createUtf8Char(0x5C));//'\x5C'); hash.set(CHAR_CAPITAL_N, createUtf8Char(0x85));//'\x85'); hash.set(CHAR_UNDERSCORE, createUtf8Char(0xA0));//'\xA0'); hash.set(CHAR_CAPITAL_L, createUtf8Char(0x2028));//'\u2028'); hash.set(CHAR_CAPITAL_P, createUtf8Char(0x2029));//'\u2029'); hash; }; static function createUtf8Char(hex:Int):String { var utf8 = new Utf8(1); utf8.addChar(hex); return utf8.toString(); } public static var HEXADECIMAL_ESCAPE_SEQUENCES:IntMap = { var hash = new IntMap(); hash.set(CHAR_SMALL_X, 2); hash.set(CHAR_SMALL_U, 4); hash.set(CHAR_CAPITAL_U, 8); hash; }; #if (eval || neko || cpp || display) public static var PATTERN_NON_PRINTABLE = ~/[\x{00}-\x{08}\x{0B}\x{0C}\x{0E}-\x{1F}\x{7F}-\x{84}\x{86}-\x{9F}\x{FFFE}\x{FFFF}]/u; #elseif (js || flash9 || java) public static var PATTERN_NON_PRINTABLE = ~/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uD800-\uDFFF\uFFFE\uFFFF]/u; #else #error "Compilation target not supported due to lack of Unicode RegEx support." #end #if (eval || neko || cpp || display) public static var PATTERN_NON_ASCII_LINE_BREAKS = ~/[\x{85}\x{2028}\x{2029}]/u; #elseif (js || flash9 || java) public static var PATTERN_NON_ASCII_LINE_BREAKS = ~/[\x85\u2028\u2029]/u; #else #error "Compilation target not supported due to lack of Unicode RegEx support." #end public static var PATTERN_FLOW_INDICATORS = ~/[,\[\]\{\}]/u; public static var PATTERN_TAG_HANDLE = ~/^(?:!|!!|![a-z\-]+!)$/iu; public static var PATTERN_TAG_URI = ~/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/iu; }