292 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			292 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | /* | ||
|  |  * Copyright (C)2005-2019 Haxe Foundation | ||
|  |  * | ||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a | ||
|  |  * copy of this software and associated documentation files (the "Software"), | ||
|  |  * to deal in the Software without restriction, including without limitation | ||
|  |  * the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
|  |  * and/or sell copies of the Software, and to permit persons to whom the | ||
|  |  * Software is furnished to do so, subject to the following conditions: | ||
|  |  * | ||
|  |  * The above copyright notice and this permission notice shall be included in | ||
|  |  * all copies or substantial portions of the Software. | ||
|  |  * | ||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
|  |  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
|  |  * DEALINGS IN THE SOFTWARE. | ||
|  |  */ | ||
|  | 
 | ||
|  | package haxe.format; | ||
|  | 
 | ||
|  | /** | ||
|  | 	An implementation of JSON printer in Haxe. | ||
|  | 
 | ||
|  | 	This class is used by `haxe.Json` when native JSON implementation | ||
|  | 	is not available. | ||
|  | 
 | ||
|  | 	@see https://haxe.org/manual/std-Json-encoding.html | ||
|  | **/ | ||
|  | class JsonPrinter { | ||
|  | 	/** | ||
|  | 		Encodes `o`'s value and returns the resulting JSON string.
 | ||
|  | 
 | ||
|  | 		If `replacer` is given and is not null, it is used to retrieve | ||
|  | 		actual object to be encoded. The `replacer` function takes two parameters, | ||
|  | 		the key and the value being encoded. Initial key value is an empty string. | ||
|  | 
 | ||
|  | 		If `space` is given and is not null, the result will be pretty-printed. | ||
|  | 		Successive levels will be indented by this string. | ||
|  | 	**/ | ||
|  | 	static public function print(o:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String):String { | ||
|  | 		var printer = new JsonPrinter(replacer, space); | ||
|  | 		printer.write("", o); | ||
|  | 		return printer.buf.toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var buf:#if flash flash.utils.ByteArray #else StringBuf #end; | ||
|  | 	var replacer:(key:Dynamic, value:Dynamic) -> Dynamic; | ||
|  | 	var indent:String; | ||
|  | 	var pretty:Bool; | ||
|  | 	var nind:Int; | ||
|  | 
 | ||
|  | 	function new(replacer:(key:Dynamic, value:Dynamic) -> Dynamic, space:String) { | ||
|  | 		this.replacer = replacer; | ||
|  | 		this.indent = space; | ||
|  | 		this.pretty = space != null; | ||
|  | 		this.nind = 0; | ||
|  | 
 | ||
|  | 		#if flash | ||
|  | 		buf = new flash.utils.ByteArray(); | ||
|  | 		buf.endian = flash.utils.Endian.BIG_ENDIAN; | ||
|  | 		buf.position = 0; | ||
|  | 		#else | ||
|  | 		buf = new StringBuf(); | ||
|  | 		#end | ||
|  | 	} | ||
|  | 
 | ||
|  | 	inline function ipad():Void { | ||
|  | 		if (pretty) | ||
|  | 			add(StringTools.lpad('', indent, nind * indent.length)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	inline function newl():Void { | ||
|  | 		if (pretty) | ||
|  | 			addChar('\n'.code); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function write(k:Dynamic, v:Dynamic) { | ||
|  | 		if (replacer != null) | ||
|  | 			v = replacer(k, v); | ||
|  | 		switch (Type.typeof(v)) { | ||
|  | 			case TUnknown: | ||
|  | 				add('"???"'); | ||
|  | 			case TObject: | ||
|  | 				objString(v); | ||
|  | 			case TInt: | ||
|  | 				add(#if (jvm || hl) Std.string(v) #else v #end); | ||
|  | 			case TFloat: | ||
|  | 				add(Math.isFinite(v) ? Std.string(v) : 'null'); | ||
|  | 			case TFunction: | ||
|  | 				add('"<fun>"'); | ||
|  | 			case TClass(c): | ||
|  | 				if (c == String) | ||
|  | 					quote(v); | ||
|  | 				else if (c == Array) { | ||
|  | 					var v:Array<Dynamic> = v; | ||
|  | 					addChar('['.code); | ||
|  | 
 | ||
|  | 					var len = v.length; | ||
|  | 					var last = len - 1; | ||
|  | 					for (i in 0...len) { | ||
|  | 						if (i > 0) | ||
|  | 							addChar(','.code) | ||
|  | 						else | ||
|  | 							nind++; | ||
|  | 						newl(); | ||
|  | 						ipad(); | ||
|  | 						write(i, v[i]); | ||
|  | 						if (i == last) { | ||
|  | 							nind--; | ||
|  | 							newl(); | ||
|  | 							ipad(); | ||
|  | 						} | ||
|  | 					} | ||
|  | 					addChar(']'.code); | ||
|  | 				} else if (c == haxe.ds.StringMap) { | ||
|  | 					var v:haxe.ds.StringMap<Dynamic> = v; | ||
|  | 					var o = {}; | ||
|  | 					for (k in v.keys()) | ||
|  | 						Reflect.setField(o, k, v.get(k)); | ||
|  | 					objString(o); | ||
|  | 				} else if (c == Date) { | ||
|  | 					var v:Date = v; | ||
|  | 					quote(v.toString()); | ||
|  | 				} else | ||
|  | 					classString(v); | ||
|  | 			case TEnum(_): | ||
|  | 				var i:Dynamic = Type.enumIndex(v); | ||
|  | 				add(i); | ||
|  | 			case TBool: | ||
|  | 				add(#if (php || jvm || hl) (v ? 'true' : 'false') #else v #end); | ||
|  | 			case TNull: | ||
|  | 				add('null'); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	extern inline function addChar(c:Int) { | ||
|  | 		#if flash | ||
|  | 		buf.writeByte(c); | ||
|  | 		#else | ||
|  | 		buf.addChar(c); | ||
|  | 		#end | ||
|  | 	} | ||
|  | 
 | ||
|  | 	extern inline function add(v:String) { | ||
|  | 		#if flash | ||
|  | 		// argument is not always a string but will be automatically casted | ||
|  | 		buf.writeUTFBytes(v); | ||
|  | 		#else | ||
|  | 		buf.add(v); | ||
|  | 		#end | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function classString(v:Dynamic) { | ||
|  | 		fieldsString(v, Type.getInstanceFields(Type.getClass(v))); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	inline function objString(v:Dynamic) { | ||
|  | 		fieldsString(v, Reflect.fields(v)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function fieldsString(v:Dynamic, fields:Array<String>) { | ||
|  | 		addChar('{'.code); | ||
|  | 		var len = fields.length; | ||
|  | 		var last = len - 1; | ||
|  | 		var first = true; | ||
|  | 		for (i in 0...len) { | ||
|  | 			var f = fields[i]; | ||
|  | 			var value = Reflect.field(v, f); | ||
|  | 			if (Reflect.isFunction(value)) | ||
|  | 				continue; | ||
|  | 			if (first) { | ||
|  | 				nind++; | ||
|  | 				first = false; | ||
|  | 			} else | ||
|  | 				addChar(','.code); | ||
|  | 			newl(); | ||
|  | 			ipad(); | ||
|  | 			quote(f); | ||
|  | 			addChar(':'.code); | ||
|  | 			if (pretty) | ||
|  | 				addChar(' '.code); | ||
|  | 			write(f, value); | ||
|  | 			if (i == last) { | ||
|  | 				nind--; | ||
|  | 				newl(); | ||
|  | 				ipad(); | ||
|  | 			} | ||
|  | 		} | ||
|  | 		addChar('}'.code); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function quote(s:String) { | ||
|  | 		#if neko | ||
|  | 		if (s.length != neko.Utf8.length(s)) { | ||
|  | 			quoteUtf8(s); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		#end | ||
|  | 		addChar('"'.code); | ||
|  | 		var i = 0; | ||
|  | 		var length = s.length; | ||
|  | 		#if hl | ||
|  | 		var prev = -1; | ||
|  | 		#end | ||
|  | 		while (i < length) { | ||
|  | 			var c = StringTools.unsafeCodeAt(s, i++); | ||
|  | 			switch (c) { | ||
|  | 				case '"'.code: | ||
|  | 					add('\\"'); | ||
|  | 				case '\\'.code: | ||
|  | 					add('\\\\'); | ||
|  | 				case '\n'.code: | ||
|  | 					add('\\n'); | ||
|  | 				case '\r'.code: | ||
|  | 					add('\\r'); | ||
|  | 				case '\t'.code: | ||
|  | 					add('\\t'); | ||
|  | 				case 8: | ||
|  | 					add('\\b'); | ||
|  | 				case 12: | ||
|  | 					add('\\f'); | ||
|  | 				default: | ||
|  | 					#if flash | ||
|  | 					if (c >= 128) | ||
|  | 						add(String.fromCharCode(c)) | ||
|  | 					else | ||
|  | 						addChar(c); | ||
|  | 					#elseif hl | ||
|  | 					if (prev >= 0) { | ||
|  | 						if (c >= 0xD800 && c <= 0xDFFF) { | ||
|  | 							addChar((((prev - 0xD800) << 10) | (c - 0xDC00)) + 0x10000); | ||
|  | 							prev = -1; | ||
|  | 						} else { | ||
|  | 							addChar("□".code); | ||
|  | 							prev = c; | ||
|  | 						} | ||
|  | 					} else { | ||
|  | 						if (c >= 0xD800 && c <= 0xDFFF) | ||
|  | 							prev = c; | ||
|  | 						else | ||
|  | 							addChar(c); | ||
|  | 					} | ||
|  | 					#else | ||
|  | 					addChar(c); | ||
|  | 					#end | ||
|  | 			} | ||
|  | 		} | ||
|  | 		#if hl | ||
|  | 		if (prev >= 0) | ||
|  | 			addChar("□".code); | ||
|  | 		#end | ||
|  | 		addChar('"'.code); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	#if neko | ||
|  | 	function quoteUtf8(s:String) { | ||
|  | 		var u = new neko.Utf8(); | ||
|  | 		neko.Utf8.iter(s, function(c) { | ||
|  | 			switch (c) { | ||
|  | 				case '\\'.code, '"'.code: | ||
|  | 					u.addChar('\\'.code); | ||
|  | 					u.addChar(c); | ||
|  | 				case '\n'.code: | ||
|  | 					u.addChar('\\'.code); | ||
|  | 					u.addChar('n'.code); | ||
|  | 				case '\r'.code: | ||
|  | 					u.addChar('\\'.code); | ||
|  | 					u.addChar('r'.code); | ||
|  | 				case '\t'.code: | ||
|  | 					u.addChar('\\'.code); | ||
|  | 					u.addChar('t'.code); | ||
|  | 				case 8: | ||
|  | 					u.addChar('\\'.code); | ||
|  | 					u.addChar('b'.code); | ||
|  | 				case 12: | ||
|  | 					u.addChar('\\'.code); | ||
|  | 					u.addChar('f'.code); | ||
|  | 				default: | ||
|  | 					u.addChar(c); | ||
|  | 			} | ||
|  | 		}); | ||
|  | 		buf.add('"'); | ||
|  | 		buf.add(u.toString()); | ||
|  | 		buf.add('"'); | ||
|  | 	} | ||
|  | 	#end | ||
|  | } |