├── lib └── as3game.swc ├── bin ├── SpriteSheet.swf └── data │ ├── xml │ ├── xmlformat.png │ └── xmlformat.xml │ ├── json │ ├── jsonformat.png │ └── jsonformat.json │ └── json-array │ ├── jsonarrayformat.json │ └── jsonarrayformat.png ├── src ├── com │ ├── as3game │ │ └── spritesheet │ │ │ ├── SpriteFrame.as │ │ │ ├── vos │ │ │ ├── DataFormat.as │ │ │ └── Animation.as │ │ │ ├── analyze │ │ │ ├── XmlFormat.as │ │ │ ├── JsonArrayFormat.as │ │ │ └── JsonFormat.as │ │ │ ├── TextureAtlas.as │ │ │ ├── SpriteSheetButton.as │ │ │ └── SpriteSheet.as │ └── adobe │ │ └── serialization │ │ └── json │ │ ├── JSONTokenType.as │ │ ├── JSONParseError.as │ │ ├── JSONToken.as │ │ ├── JSON.as │ │ ├── JSONEncoder.as │ │ ├── JSONDecoder.as │ │ └── JSONTokenizer.as ├── TestSpriteSheet.as └── SWFProfiler.as ├── SpriteSheet.lxml ├── .gitattributes ├── README.md ├── SpriteSheet.as3proj └── .gitignore /lib/as3game.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/lib/as3game.swc -------------------------------------------------------------------------------- /bin/SpriteSheet.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/bin/SpriteSheet.swf -------------------------------------------------------------------------------- /bin/data/xml/xmlformat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/bin/data/xml/xmlformat.png -------------------------------------------------------------------------------- /bin/data/xml/xmlformat.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/bin/data/xml/xmlformat.xml -------------------------------------------------------------------------------- /bin/data/json/jsonformat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/bin/data/json/jsonformat.png -------------------------------------------------------------------------------- /bin/data/json/jsonformat.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/bin/data/json/jsonformat.json -------------------------------------------------------------------------------- /bin/data/json-array/jsonarrayformat.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/bin/data/json-array/jsonarrayformat.json -------------------------------------------------------------------------------- /bin/data/json-array/jsonarrayformat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerzhu/SpriteSheet/HEAD/bin/data/json-array/jsonarrayformat.png -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/SpriteFrame.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet 2 | { 3 | /** 4 | * 动画一帧对应PNG文件中的属性 5 | * @author Tylerzhu 6 | */ 7 | public class SpriteFrame 8 | { 9 | public var id:String; 10 | public var x:int; 11 | public var y:int; 12 | public var width:int; 13 | public var height:int; 14 | public var offX:int; 15 | public var offY:int; 16 | public var centerX:Number; 17 | public var centerY:Number; 18 | 19 | public function SpriteFrame() 20 | { 21 | 22 | } 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/vos/DataFormat.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet.vos 2 | { 3 | /** 4 | * 与Flash cs导出的数据格式对应,目前实现了以下3种 5 | * 1. JSON 6 | * 2. JSON-Array 7 | * 3. XML 8 | * @author Tylerzhu 9 | */ 10 | public class DataFormat 11 | { 12 | public static const FORMAT_JSON:String = "format_json"; 13 | public static const FORMAT_JSON_ARRAY:String = "format_json_array"; 14 | public static const FORMAT_XML:String = "format_xml"; 15 | 16 | public function DataFormat() 17 | { 18 | 19 | } 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /SpriteSheet.lxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | false 7 | false 8 | true 9 | false 10 | false 11 | 12 | 13 | None 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SpriteSheet 2 | =========== 3 | 4 | ActionScript 3.0 Sprite Sheet render engine 5 | 6 | 实现了SpriteSheet精灵序列图引擎,可以解析Flash Cs6/TexturePacker导出的JSON、JSON-Array、XML格式的SpriteSheet。 7 | 相关介绍:http://www.cnblogs.com/skynet/p/3570421.html 8 | 9 | SpriteSheet调整中心等功能,参考SpriteSheet编辑工具:https://github.com/saylorzhu/SpriteSheetEdit 10 | 11 | Demo 12 | ----------- 13 | . 加载SpriteSheet png图片及数据描述文件 14 | 15 | >AssetManager.getInstance().getGroupAssets("spritesheets-json", ["data/json/jsonformat.json", "data/json/jsonformat.png"], onAnimLoaded); 16 | 17 | . 创建SpriteSheet实例: 18 | 19 | >private function onAnimLoaded():void 20 | { 21 | var bitmapData:BitmapData = AssetManager.getInstance().bulkLoader.getBitmapData("data/json/jsonformat.png"); 22 | var sheets:* = AssetManager.getInstance().getContent("data/json/jsonformat.json"); 23 | var sp:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON); 24 | sp.setAction("呼吸", 14); 25 | sp.play(); 26 | addChild(sp); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/vos/Animation.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet.vos 2 | { 3 | import com.as3game.spritesheet.SpriteFrame; 4 | import flash.geom.Point; 5 | /** 6 | * 动画信息,一张SpriteSheet中经常会包含多个动画序列 7 | * 如一个战斗角色SpriteSheet包含:walk、stand、attack、unattack等等 8 | * @author Tylerzhu 9 | */ 10 | public class Animation 11 | { 12 | public var seqName:String;// 动画序列名 (e.g. "walk") 13 | public var delay:Number;// 帧间隔 14 | public var loop:Boolean;// 是否循环播放 15 | public var arFrames:Vector.;// 帧信息数据 16 | public var center:Point; //中心点位置 17 | 18 | public function Animation(name:String, frames:Vector., frameRate:Number=0, looped:Boolean=true) 19 | { 20 | seqName = name; 21 | delay = 0; 22 | if(frameRate > 0) 23 | delay = 1.0/frameRate; 24 | 25 | arFrames = frames; 26 | loop = looped; 27 | center = new Point(frames[1].centerX, frames[1].centerY); 28 | } 29 | 30 | public function destroy():void 31 | { 32 | arFrames = null; 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/analyze/XmlFormat.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet.analyze 2 | { 3 | import com.as3game.spritesheet.SpriteFrame; 4 | 5 | /** 6 | * 解析XML格式的SpriteSheet数据描述文件 7 | * @author Tylerzhu 8 | */ 9 | public class XmlFormat 10 | { 11 | private var mMeta:Object; 12 | private var mFrames:Vector.; 13 | 14 | public function XmlFormat(xml:XML) 15 | { 16 | mFrames = new Vector.(); 17 | var spriteFrame:SpriteFrame; 18 | 19 | var xmlList:XMLList = xml.SubTexture; 20 | for (var i:int = 0; i < xmlList.length(); i++) 21 | { 22 | spriteFrame = new SpriteFrame(); 23 | spriteFrame.id = String(xmlList[i].@name); 24 | spriteFrame.x = int(xmlList[i].@x); 25 | spriteFrame.y = int(xmlList[i].@y); 26 | spriteFrame.width = int(xmlList[i].@width); 27 | spriteFrame.height = int(xmlList[i].@height); 28 | spriteFrame.offX = -1 * int(xmlList[i].@frameX); 29 | spriteFrame.offY = -1 * int(xmlList[i].@frameY); 30 | spriteFrame.centerX = Number(xmlList[i].@centerX); 31 | spriteFrame.centerY = Number(xmlList[i].@centerY); 32 | mFrames.push(spriteFrame); 33 | } 34 | } 35 | 36 | public function getData():Object 37 | { 38 | return {"meta": mMeta, "frames": mFrames}; 39 | } 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/analyze/JsonArrayFormat.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet.analyze 2 | { 3 | import com.adobe.serialization.json.JSON; 4 | import com.as3game.spritesheet.SpriteFrame; 5 | 6 | /** 7 | * 解析JSON Array格式的SpriteSheet数据描述文件 8 | * @author Tylerzhu 9 | */ 10 | public class JsonArrayFormat 11 | { 12 | private var mMeta:Object; 13 | private var mFrames:Vector.; 14 | 15 | public function JsonArrayFormat(text:String) 16 | { 17 | var data:Object = com.adobe.serialization.json.JSON.decode(text); 18 | mMeta = data.meta; 19 | mFrames = new Vector.(); 20 | 21 | var frames:Object = data.frames; 22 | var spriteFrame:SpriteFrame; 23 | for each (var item:Object in frames) 24 | { 25 | spriteFrame = new SpriteFrame(); 26 | spriteFrame.id = item.filename; 27 | spriteFrame.x = item.frame.x; 28 | spriteFrame.y = item.frame.y; 29 | spriteFrame.width = item.frame.w; 30 | spriteFrame.height = item.frame.h; 31 | spriteFrame.offX = item.spriteSourceSize.x; 32 | spriteFrame.offY = item.spriteSourceSize.y; 33 | //spriteFrame.centerX = Number(xmlList[i].@centerX); 34 | //spriteFrame.centerY = Number(xmlList[i].@centerY); 35 | mFrames.push(spriteFrame); 36 | } 37 | } 38 | 39 | public function getData():Object 40 | { 41 | return {"meta": mMeta, "frames": mFrames}; 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/analyze/JsonFormat.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet.analyze 2 | { 3 | import com.adobe.serialization.json.JSON; 4 | import com.as3game.spritesheet.SpriteFrame; 5 | 6 | /** 7 | * 解析JSON格式的SpriteSheet数据描述文件 8 | * @author Tylerzhu 9 | */ 10 | public class JsonFormat 11 | { 12 | private var mMeta:Object; 13 | private var mFrames:Vector.; 14 | 15 | public function JsonFormat(text:String) 16 | { 17 | var data:Object = com.adobe.serialization.json.JSON.decode(text); 18 | mMeta = data.meta; 19 | mFrames = new Vector.(); 20 | 21 | var frames:Object = data.frames; 22 | var spriteFrame:SpriteFrame; 23 | for (var key:String in frames) 24 | { 25 | var item:Object = frames[key]; 26 | spriteFrame = new SpriteFrame(); 27 | spriteFrame.id = key; 28 | spriteFrame.x = item.frame.x; 29 | spriteFrame.y = item.frame.y; 30 | spriteFrame.width = item.frame.w; 31 | spriteFrame.height = item.frame.h; 32 | spriteFrame.offX = item.spriteSourceSize.x; 33 | spriteFrame.offY = item.spriteSourceSize.y; 34 | //spriteFrame.centerX = Number(xmlList[i].@centerX); 35 | //spriteFrame.centerY = Number(xmlList[i].@centerY); 36 | mFrames.push(spriteFrame); 37 | } 38 | } 39 | 40 | public function getData():Object 41 | { 42 | return { "meta": mMeta, "frames":mFrames }; 43 | } 44 | 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/TextureAtlas.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet 2 | { 3 | import com.as3game.spritesheet.vos.Animation; 4 | import flash.display.BitmapData; 5 | import flash.geom.Point; 6 | import flash.geom.Rectangle; 7 | 8 | /** 9 | * 动画序列帧数据 10 | * @author Tylerzhu 11 | */ 12 | public class TextureAtlas 13 | { 14 | private var mTextureRegions:Vector.; 15 | private var mFrameOffsets:Vector.; 16 | private var mTextureSheet:BitmapData; 17 | private var mFrameRect:Rectangle; 18 | 19 | public function TextureAtlas() 20 | { 21 | 22 | } 23 | 24 | /** 25 | * 初始化动画帧序列,构建帧框数组、偏移点数组 26 | * @param sheet 27 | * @param animation 28 | */ 29 | public function init(sheet:BitmapData, animation:Animation):void 30 | { 31 | mTextureRegions = new Vector.(); 32 | mFrameRect = new Rectangle(); 33 | mFrameOffsets = new Vector.(); 34 | mTextureSheet = sheet; 35 | var arFrameData:Vector. = animation.arFrames; 36 | var rcFrame:Rectangle; 37 | var regPt:Point; 38 | 39 | for (var i:int = 0; i < arFrameData.length; i++) 40 | { 41 | rcFrame = new Rectangle(); 42 | 43 | rcFrame.x = arFrameData[i].x; 44 | rcFrame.y = arFrameData[i].y; 45 | rcFrame.width = arFrameData[i].width; 46 | rcFrame.height = arFrameData[i].height; 47 | mTextureRegions.push(rcFrame); 48 | 49 | regPt = new Point(); 50 | regPt.x = arFrameData[i].offX; 51 | regPt.y = arFrameData[i].offY; 52 | mFrameOffsets.push(regPt); 53 | 54 | mFrameRect.width = Math.max(mFrameRect.width, rcFrame.width + regPt.x); 55 | mFrameRect.height = Math.max(mFrameRect.height, rcFrame.height + regPt.y); 56 | } 57 | 58 | } 59 | 60 | /** 61 | * 62 | * @param frame 63 | * @param dstBmp 64 | */ 65 | public function drawFrame(frame:uint, dstBmp:BitmapData):void 66 | { 67 | dstBmp.fillRect(dstBmp.rect, 0); 68 | dstBmp.copyPixels(mTextureSheet, mTextureRegions[frame], mFrameOffsets[frame]); 69 | } 70 | 71 | public function get maxRect():Rectangle 72 | { 73 | return mFrameRect; 74 | } 75 | 76 | public function destroy():void 77 | { 78 | mTextureRegions = null; 79 | mFrameRect = null; 80 | mFrameOffsets = null; 81 | mTextureSheet = null; 82 | } 83 | 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONTokenType.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | /** 37 | * Class containing constant values for the different types 38 | * of tokens in a JSON encoded string. 39 | */ 40 | public final class JSONTokenType 41 | { 42 | public static const UNKNOWN:int = -1; 43 | 44 | public static const COMMA:int = 0; 45 | 46 | public static const LEFT_BRACE:int = 1; 47 | 48 | public static const RIGHT_BRACE:int = 2; 49 | 50 | public static const LEFT_BRACKET:int = 3; 51 | 52 | public static const RIGHT_BRACKET:int = 4; 53 | 54 | public static const COLON:int = 6; 55 | 56 | public static const TRUE:int = 7; 57 | 58 | public static const FALSE:int = 8; 59 | 60 | public static const NULL:int = 9; 61 | 62 | public static const STRING:int = 10; 63 | 64 | public static const NUMBER:int = 11; 65 | 66 | public static const NAN:int = 12; 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONParseError.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | /** 37 | * 38 | * 39 | */ 40 | public class JSONParseError extends Error 41 | { 42 | 43 | /** The location in the string where the error occurred */ 44 | private var _location:int; 45 | 46 | /** The string in which the parse error occurred */ 47 | private var _text:String; 48 | 49 | /** 50 | * Constructs a new JSONParseError. 51 | * 52 | * @param message The error message that occured during parsing 53 | * @langversion ActionScript 3.0 54 | * @playerversion Flash 9.0 55 | * @tiptext 56 | */ 57 | public function JSONParseError( message:String = "", location:int = 0, text:String = "" ) 58 | { 59 | super( message ); 60 | name = "JSONParseError"; 61 | _location = location; 62 | _text = text; 63 | } 64 | 65 | /** 66 | * Provides read-only access to the location variable. 67 | * 68 | * @return The location in the string where the error occurred 69 | * @langversion ActionScript 3.0 70 | * @playerversion Flash 9.0 71 | * @tiptext 72 | */ 73 | public function get location():int 74 | { 75 | return _location; 76 | } 77 | 78 | /** 79 | * Provides read-only access to the text variable. 80 | * 81 | * @return The string in which the error occurred 82 | * @langversion ActionScript 3.0 83 | * @playerversion Flash 9.0 84 | * @tiptext 85 | */ 86 | public function get text():String 87 | { 88 | return _text; 89 | } 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONToken.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | public final class JSONToken 37 | { 38 | 39 | /** 40 | * The type of the token. 41 | * 42 | * @langversion ActionScript 3.0 43 | * @playerversion Flash 9.0 44 | * @tiptext 45 | */ 46 | public var type:int; 47 | 48 | /** 49 | * The value of the token 50 | * 51 | * @langversion ActionScript 3.0 52 | * @playerversion Flash 9.0 53 | * @tiptext 54 | */ 55 | public var value:Object; 56 | 57 | /** 58 | * Creates a new JSONToken with a specific token type and value. 59 | * 60 | * @param type The JSONTokenType of the token 61 | * @param value The value of the token 62 | * @langversion ActionScript 3.0 63 | * @playerversion Flash 9.0 64 | * @tiptext 65 | */ 66 | public function JSONToken( type:int = -1 /* JSONTokenType.UNKNOWN */, value:Object = null ) 67 | { 68 | this.type = type; 69 | this.value = value; 70 | } 71 | 72 | /** 73 | * Reusable token instance. 74 | * 75 | * @see #create() 76 | */ 77 | internal static const token:JSONToken = new JSONToken(); 78 | 79 | /** 80 | * Factory method to create instances. Because we don't need more than one instance 81 | * of a token at a time, we can always use the same instance to improve performance 82 | * and reduce memory consumption during decoding. 83 | */ 84 | internal static function create( type:int = -1 /* JSONTokenType.UNKNOWN */, value:Object = null ):JSONToken 85 | { 86 | token.type = type; 87 | token.value = value; 88 | 89 | return token; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /SpriteSheet.as3proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSON.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | /** 37 | * This class provides encoding and decoding of the JSON format. 38 | * 39 | * Example usage: 40 | * 41 | * // create a JSON string from an internal object 42 | * JSON.encode( myObject ); 43 | * 44 | * // read a JSON string into an internal object 45 | * var myObject:Object = JSON.decode( jsonString ); 46 | * 47 | */ 48 | public final class JSON 49 | { 50 | /** 51 | * Encodes a object into a JSON string. 52 | * 53 | * @param o The object to create a JSON string for 54 | * @return the JSON string representing o 55 | * @langversion ActionScript 3.0 56 | * @playerversion Flash 9.0 57 | * @tiptext 58 | */ 59 | public static function encode( o:Object ):String 60 | { 61 | return new JSONEncoder( o ).getString(); 62 | } 63 | 64 | /** 65 | * Decodes a JSON string into a native object. 66 | * 67 | * @param s The JSON string representing the object 68 | * @param strict Flag indicating if the decoder should strictly adhere 69 | * to the JSON standard or not. The default of true 70 | * throws errors if the format does not match the JSON syntax exactly. 71 | * Pass false to allow for non-properly-formatted JSON 72 | * strings to be decoded with more leniancy. 73 | * @return A native object as specified by s 74 | * @throw JSONParseError 75 | * @langversion ActionScript 3.0 76 | * @playerversion Flash 9.0 77 | * @tiptext 78 | */ 79 | public static function decode( s:String, strict:Boolean = true ):* 80 | { 81 | return new JSONDecoder( s, strict ).getValue(); 82 | } 83 | 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/SpriteSheetButton.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet 2 | { 3 | import com.as3game.spritesheet.analyze.JsonArrayFormat; 4 | import com.as3game.spritesheet.analyze.JsonFormat; 5 | import com.as3game.spritesheet.analyze.XmlFormat; 6 | import com.as3game.spritesheet.vos.Animation; 7 | import com.as3game.spritesheet.vos.DataFormat; 8 | import flash.display.Bitmap; 9 | import flash.display.BitmapData; 10 | import flash.display.Sprite; 11 | import flash.events.MouseEvent; 12 | import flash.geom.Point; 13 | 14 | /** 15 | * ... 16 | * @author Tylerzhu 17 | */ 18 | public class SpriteSheetButton extends Sprite 19 | { 20 | private var mTexture:BitmapData; //SpriteSheet位图 21 | private var mSheets:Object; //SpriteSheet描述数据 22 | private var mTextureAtlas:TextureAtlas; 23 | private var mCanvas:Bitmap; //画布 24 | private var mButtonName:String; //按钮名称(默认为all) 25 | private var mAnimation:Animation; //按钮帧序列 26 | 27 | public function SpriteSheetButton(texture:BitmapData, sheets:*, dataFormat:String = "format_xml", buttonName:String = "all") 28 | { 29 | mButtonName = buttonName; 30 | mTexture = texture; 31 | switch (dataFormat) 32 | { 33 | case DataFormat.FORMAT_JSON: 34 | mSheets = new JsonFormat(sheets as String).getData(); 35 | break; 36 | case DataFormat.FORMAT_JSON_ARRAY: 37 | mSheets = new JsonArrayFormat(sheets as String).getData(); 38 | break; 39 | case DataFormat.FORMAT_XML: 40 | mSheets = new XmlFormat(sheets as XML).getData(); 41 | break; 42 | default: 43 | } 44 | 45 | initButton(buttonName); 46 | } 47 | 48 | public function get sheets():Object 49 | { 50 | return mSheets; 51 | } 52 | 53 | public function get centor():Point 54 | { 55 | return mAnimation.center; 56 | } 57 | 58 | public function destroy():void 59 | { 60 | mCanvas.bitmapData.dispose(); 61 | mCanvas = null; 62 | mTexture = null; 63 | mButtonName = null; 64 | mSheets = null; 65 | mAnimation.destroy(); 66 | mAnimation = null; 67 | } 68 | 69 | private function initButton(buttonName:String):void 70 | { 71 | var frames:Vector. = new Vector.(); 72 | for each (var item:SpriteFrame in mSheets.frames) 73 | { 74 | if (item.id.indexOf(mButtonName) != -1 || mButtonName == "all") 75 | { 76 | frames.push(item); 77 | } 78 | } 79 | 80 | mAnimation = new Animation(mButtonName, frames); 81 | mTextureAtlas = new TextureAtlas(); 82 | mTextureAtlas.init(mTexture, mAnimation); 83 | 84 | mCanvas = new Bitmap(); 85 | mCanvas.bitmapData = new BitmapData(mTextureAtlas.maxRect.width, mTextureAtlas.maxRect.height); 86 | mCanvas.x = mAnimation.center.x; 87 | mCanvas.y = mAnimation.center.y; 88 | addChild(mCanvas); 89 | 90 | mTextureAtlas.drawFrame(0, mCanvas.bitmapData); 91 | 92 | this.buttonMode = true; 93 | this.addEventListener(MouseEvent.MOUSE_OVER, onMouseOver); 94 | this.addEventListener(MouseEvent.MOUSE_OUT, onMouseOut); 95 | this.addEventListener(MouseEvent.CLICK, onMouseClick); 96 | } 97 | 98 | private function onMouseClick(e:MouseEvent):void 99 | { 100 | mTextureAtlas.drawFrame(0, mCanvas.bitmapData); 101 | } 102 | 103 | private function onMouseOut(e:MouseEvent):void 104 | { 105 | mTextureAtlas.drawFrame(0, mCanvas.bitmapData); 106 | } 107 | 108 | private function onMouseOver(e:MouseEvent):void 109 | { 110 | mTextureAtlas.drawFrame(1, mCanvas.bitmapData); 111 | } 112 | 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .classpath 15 | .settings/ 16 | .loadpath 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # CDT-specific 25 | .cproject 26 | 27 | # PDT-specific 28 | .buildpath 29 | 30 | 31 | ################# 32 | ## Visual Studio 33 | ################# 34 | 35 | ## Ignore Visual Studio temporary files, build results, and 36 | ## files generated by popular Visual Studio add-ons. 37 | 38 | # User-specific files 39 | *.suo 40 | *.user 41 | *.sln.docstates 42 | 43 | # Build results 44 | 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | x64/ 48 | build/ 49 | [Oo]bj/ 50 | 51 | # MSTest test Results 52 | [Tt]est[Rr]esult*/ 53 | [Bb]uild[Ll]og.* 54 | 55 | *_i.c 56 | *_p.c 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.log 77 | *.scc 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | 99 | # TeamCity is a build add-in 100 | _TeamCity* 101 | 102 | # DotCover is a Code Coverage Tool 103 | *.dotCover 104 | 105 | # NCrunch 106 | *.ncrunch* 107 | .*crunch*.local.xml 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.Publish.xml 127 | *.pubxml 128 | 129 | # NuGet Packages Directory 130 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 131 | #packages/ 132 | 133 | # Windows Azure Build Output 134 | csx 135 | *.build.csdef 136 | 137 | # Windows Store app package directory 138 | AppPackages/ 139 | 140 | # Others 141 | sql/ 142 | *.Cache 143 | ClientBin/ 144 | [Ss]tyle[Cc]op.* 145 | ~$* 146 | *~ 147 | *.dbmdl 148 | *.[Pp]ublish.xml 149 | *.pfx 150 | *.publishsettings 151 | 152 | # RIA/Silverlight projects 153 | Generated_Code/ 154 | 155 | # Backup & report files from converting an old project file to a newer 156 | # Visual Studio version. Backup files are not needed, because we have git ;-) 157 | _UpgradeReport_Files/ 158 | Backup*/ 159 | UpgradeLog*.XML 160 | UpgradeLog*.htm 161 | 162 | # SQL Server files 163 | App_Data/*.mdf 164 | App_Data/*.ldf 165 | 166 | ############# 167 | ## Windows detritus 168 | ############# 169 | 170 | # Windows image file caches 171 | Thumbs.db 172 | ehthumbs.db 173 | 174 | # Folder config file 175 | Desktop.ini 176 | 177 | # Recycle Bin used on file shares 178 | $RECYCLE.BIN/ 179 | 180 | # Mac crap 181 | .DS_Store 182 | 183 | 184 | ############# 185 | ## Python 186 | ############# 187 | 188 | *.py[co] 189 | 190 | # Packages 191 | *.egg 192 | *.egg-info 193 | dist/ 194 | build/ 195 | eggs/ 196 | parts/ 197 | var/ 198 | sdist/ 199 | develop-eggs/ 200 | .installed.cfg 201 | 202 | # Installer logs 203 | pip-log.txt 204 | 205 | # Unit test / coverage reports 206 | .coverage 207 | .tox 208 | 209 | #Translations 210 | *.mo 211 | 212 | #Mr Developer 213 | .mr.developer.cfg 214 | -------------------------------------------------------------------------------- /src/com/as3game/spritesheet/SpriteSheet.as: -------------------------------------------------------------------------------- 1 | package com.as3game.spritesheet 2 | { 3 | import com.as3game.spritesheet.analyze.JsonArrayFormat; 4 | import com.as3game.spritesheet.analyze.JsonFormat; 5 | import com.as3game.spritesheet.analyze.XmlFormat; 6 | import com.as3game.spritesheet.vos.Animation; 7 | import com.as3game.spritesheet.vos.DataFormat; 8 | import com.as3game.time.GameTimer; 9 | import flash.display.Bitmap; 10 | import flash.display.BitmapData; 11 | import flash.display.Sprite; 12 | import flash.geom.Point; 13 | import flash.utils.getTimer; 14 | 15 | /** 16 | * SpriteSheet位图动画类 17 | * @author Tylerzhu 18 | */ 19 | public class SpriteSheet extends Sprite 20 | { 21 | private var mTextureAtlas:TextureAtlas; 22 | private var mPlaying:Boolean; 23 | private var mCurFrame:uint; 24 | private var mCanvas:Bitmap;//当前帧位图画布 25 | 26 | private var mTexture:BitmapData; //SpriteSheet位图 27 | private var mSheets:Object; //SpriteSheet描述数据 28 | private var mActionName:String; //当前动作名称(默认为all) 29 | private var mAnimation:Animation;//当前动画(一张SpriteSheet中经常会包含多个动画序列,walk/stand/attack) 30 | private var mObjectId:String; 31 | 32 | public function SpriteSheet(texture:BitmapData, sheets:*, dataFormat:String) 33 | { 34 | mObjectId = "SpriteSheet" + getTimer(); 35 | mActionName = ""; 36 | mTexture = texture; 37 | switch (dataFormat) 38 | { 39 | case DataFormat.FORMAT_JSON: 40 | mSheets = new JsonFormat(sheets as String).getData(); 41 | break; 42 | case DataFormat.FORMAT_JSON_ARRAY: 43 | mSheets = new JsonArrayFormat(sheets as String).getData(); 44 | break; 45 | case DataFormat.FORMAT_XML: 46 | mSheets = new XmlFormat(sheets as XML).getData(); 47 | break; 48 | default: 49 | } 50 | } 51 | 52 | public function get centor():Point 53 | { 54 | return mAnimation.center; 55 | } 56 | 57 | public function setAction(id:String = "all", frameRate:int = 30, loop:Boolean = true):void 58 | { 59 | if (id == mActionName) 60 | { 61 | return; 62 | } 63 | 64 | // 清理操作 65 | if (mCanvas) 66 | { 67 | removeChild(mCanvas); 68 | mCanvas.bitmapData.dispose(); 69 | } 70 | if (mAnimation) 71 | { 72 | mAnimation.destroy(); 73 | } 74 | if (mTextureAtlas) 75 | { 76 | mTextureAtlas.destroy(); 77 | } 78 | 79 | // 80 | mActionName = id; 81 | mCurFrame = 0; 82 | 83 | var frames:Vector. = new Vector.(); 84 | if (mActionName == "all") 85 | { 86 | frames = mSheets.frames; 87 | } 88 | else 89 | { 90 | for each (var item:SpriteFrame in mSheets.frames) 91 | { 92 | if (item.id.indexOf(id) != -1) 93 | { 94 | frames.push(item); 95 | } 96 | } 97 | } 98 | 99 | mAnimation = new Animation(id, frames, frameRate, loop); 100 | mTextureAtlas = new TextureAtlas(); 101 | mTextureAtlas.init(mTexture, mAnimation); 102 | 103 | mCanvas = new Bitmap(); 104 | mCanvas.bitmapData = new BitmapData(mTextureAtlas.maxRect.width, mTextureAtlas.maxRect.height); 105 | mCanvas.x = mAnimation.center.x; 106 | mCanvas.y = mAnimation.center.y; 107 | addChild(mCanvas); 108 | } 109 | 110 | public function get isPlaying():Boolean 111 | { 112 | return mPlaying; 113 | } 114 | 115 | /** 116 | * 播放SpriteSheet位图动画 117 | */ 118 | public function play():void 119 | { 120 | if (mActionName == "") 121 | { 122 | return; 123 | } 124 | mPlaying = true; 125 | mTextureAtlas.drawFrame(mCurFrame, mCanvas.bitmapData); 126 | 127 | GameTimer.getInstance().register(mObjectId, 1000 * mAnimation.delay, 0, update); 128 | } 129 | 130 | /** 131 | * 停止SpriteSheet位图动画 132 | */ 133 | public function stop():void 134 | { 135 | mPlaying = false; 136 | GameTimer.getInstance().unregister(mObjectId); 137 | } 138 | 139 | /** 140 | * 将播放头移到SpriteSheet的指定帧并开始播放。 141 | * @param frame 表示播放头转到的帧编号的数字。 142 | */ 143 | public function gotoAndPlay(frame:uint):void 144 | { 145 | mCurFrame = frame; 146 | if (mCurFrame >= mAnimation.arFrames.length) 147 | { 148 | mCurFrame = mAnimation.arFrames.length - 1; 149 | } 150 | play(); 151 | } 152 | 153 | /** 154 | * 将播放头移到SpriteSheet的指定帧并停在那里。 155 | * @param frame 表示播放头转到的帧编号的数字。 156 | */ 157 | public function gotoAndStop(frame:uint):void 158 | { 159 | stop(); 160 | mCurFrame = frame; 161 | if (mCurFrame >= mAnimation.arFrames.length) 162 | { 163 | mCurFrame = mAnimation.arFrames.length - 1; 164 | } 165 | mTextureAtlas.drawFrame(mCurFrame, mCanvas.bitmapData); 166 | } 167 | 168 | public function get currenFrame():uint 169 | { 170 | return mCurFrame; 171 | } 172 | 173 | public function get actionName():String 174 | { 175 | return mActionName; 176 | } 177 | 178 | private function update(count:uint):void 179 | { 180 | mCurFrame ++; 181 | if (mCurFrame >= mAnimation.arFrames.length) 182 | { 183 | if (mAnimation.loop) 184 | { 185 | mCurFrame = 0; 186 | } 187 | else 188 | { 189 | GameTimer.getInstance().unregister(mObjectId); 190 | return; 191 | } 192 | } 193 | mTextureAtlas.drawFrame(mCurFrame, mCanvas.bitmapData); 194 | 195 | } 196 | } 197 | 198 | } -------------------------------------------------------------------------------- /src/TestSpriteSheet.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import com.as3game.asset.AssetManager; 4 | import com.as3game.spritesheet.SpriteSheet; 5 | import com.as3game.spritesheet.vos.DataFormat; 6 | import flash.display.BitmapData; 7 | import flash.display.Sprite; 8 | import flash.events.MouseEvent; 9 | import flash.filters.ColorMatrixFilter; 10 | import flash.text.TextField; 11 | 12 | /** 13 | * ... 14 | * @author Tylerzhu 15 | */ 16 | public class TestSpriteSheet extends Sprite 17 | { 18 | 19 | public function TestSpriteSheet() 20 | { 21 | SWFProfiler.init(stage, this); 22 | AssetManager.getInstance().getGroupAssets("spritesheets-json", ["data/json/jsonformat.json", "data/json/jsonformat.png"], onAnimLoaded); 23 | AssetManager.getInstance().getGroupAssets("spritesheets-xml", ["data/xml/xmlformat.xml", "data/xml/xmlformat.png"], onAnimLoadedXML); 24 | AssetManager.getInstance().getGroupAssets("spritesheets-jsonarray", ["data/json-array/jsonarrayformat.json", "data/json-array/jsonarrayformat.png"], onAnimLoadedJsonArray); 25 | } 26 | 27 | 28 | private function onAnimLoaded():void 29 | { 30 | var bitmapData:BitmapData = AssetManager.getInstance().bulkLoader.getBitmapData("data/json/jsonformat.png"); 31 | var sheets:* = AssetManager.getInstance().getContent("data/json/jsonformat.json"); 32 | var sp:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON); 33 | sp.setAction("呼吸", 14); 34 | //sp.setAction("打击", 14); 35 | sp.play(); 36 | addChild(sp); 37 | 38 | var sp1:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON); 39 | sp1.x = 100; 40 | sp1.setAction("打击", 14); 41 | sp1.play(); 42 | addChild(sp1); 43 | //sp1.filters = [new ColorMatrixFilter([ 44 | //0.3,0.6,0.082,0,0, 45 | //0.3,0.6,0.082,0,0, 46 | //0.3,0.6,0.082,0,0, 47 | //0,0,0,1,0])];; 48 | 49 | var sp2:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON); 50 | sp2.x = 250; 51 | sp2.setAction("左行走", 14); 52 | sp2.play(); 53 | addChild(sp2); 54 | 55 | var sp3:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON); 56 | sp3.x = 400; 57 | sp3.setAction("挨打", 14); 58 | sp3.play(); 59 | addChild(sp3); 60 | 61 | var sp4:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON); 62 | sp4.x = 550; 63 | sp4.setAction("攻击", 30); 64 | sp4.play(); 65 | addChild(sp4); 66 | 67 | var tf:TextField = new TextField(); 68 | tf.text = "spritesheets-json"; 69 | tf.width = 200; 70 | addChild(tf); 71 | } 72 | 73 | private function onAnimLoadedXML():void 74 | { 75 | var bitmapData:BitmapData = AssetManager.getInstance().bulkLoader.getBitmapData("data/xml/xmlformat.png"); 76 | var sheets:* = AssetManager.getInstance().getContent("data/xml/xmlformat.xml"); 77 | var sp:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_XML); 78 | sp.setAction("呼吸", 15); 79 | sp.play(); 80 | sp.y = 150; 81 | addChild(sp); 82 | 83 | var sp1:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_XML); 84 | sp1.x = 100; 85 | sp1.y = 150; 86 | sp1.setAction("打击", 14); 87 | sp1.play(); 88 | addChild(sp1); 89 | 90 | var sp2:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_XML); 91 | sp2.x = 250; 92 | sp2.y = 150; 93 | sp2.setAction("左行走", 14); 94 | sp2.play(); 95 | addChild(sp2); 96 | sp2.buttonMode = true; 97 | sp2.addEventListener(MouseEvent.CLICK, onClickHandler); 98 | 99 | var sp3:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_XML); 100 | sp3.x = 400; 101 | sp3.y = 150; 102 | sp3.setAction("挨打", 14); 103 | sp3.play(); 104 | addChild(sp3); 105 | 106 | var sp4:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_XML); 107 | sp4.x = 550; 108 | sp4.y = 150; 109 | sp4.setAction("攻击", 30); 110 | sp4.play(); 111 | addChild(sp4); 112 | 113 | var tf:TextField = new TextField(); 114 | tf.width = 200; 115 | tf.text = "spritesheets-xml"; 116 | tf.y = 150; 117 | addChild(tf); 118 | } 119 | 120 | private function onClickHandler(e:MouseEvent):void 121 | { 122 | var sp:SpriteSheet = e.currentTarget as SpriteSheet; 123 | trace("~~~~~~~~~~~~~~~~~~~~~~~~~~~", sp.isPlaying); 124 | if (sp.actionName == "打击") 125 | { 126 | sp.stop(); 127 | sp.setAction("挨打", 14); 128 | sp.play(); 129 | //sp.gotoAndStop(13); 130 | trace("===========================", sp.isPlaying); 131 | } 132 | else 133 | { 134 | sp.stop(); 135 | sp.setAction("打击", 14); 136 | sp.play(); 137 | //sp.play(); 138 | //sp.gotoAndPlay(1); 139 | } 140 | } 141 | 142 | private function onAnimLoadedJsonArray():void 143 | { 144 | var bitmapData:BitmapData = AssetManager.getInstance().bulkLoader.getBitmapData("data/json-array/jsonarrayformat.png"); 145 | var sheets:* = AssetManager.getInstance().getContent("data/json-array/jsonarrayformat.json"); 146 | var sp:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON_ARRAY); 147 | sp.setAction("呼吸", 15); 148 | sp.play(); 149 | sp.y = 300; 150 | addChild(sp); 151 | 152 | var sp1:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON_ARRAY); 153 | sp1.x = 100; 154 | sp1.y = 300; 155 | sp1.setAction("打击", 14); 156 | sp1.play(); 157 | addChild(sp1); 158 | 159 | var sp2:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON_ARRAY); 160 | sp2.x = 250; 161 | sp2.y = 300; 162 | sp2.setAction("左行走", 14); 163 | sp2.play(); 164 | addChild(sp2); 165 | sp2.buttonMode = true; 166 | 167 | var sp3:SpriteSheet = new SpriteSheet(bitmapData, sheets, DataFormat.FORMAT_JSON_ARRAY); 168 | sp3.x = 400; 169 | sp3.y = 300; 170 | sp3.setAction("挨打", 14); 171 | sp3.play(); 172 | addChild(sp3); 173 | 174 | var tf:TextField = new TextField(); 175 | tf.width = 200; 176 | tf.text = "spritesheets-jsonarray"; 177 | tf.y = 300; 178 | addChild(tf); 179 | } 180 | } 181 | 182 | } -------------------------------------------------------------------------------- /src/SWFProfiler.as: -------------------------------------------------------------------------------- 1 | package { 2 | import flash.display.*; 3 | import flash.events.*; 4 | import flash.net.LocalConnection; 5 | import flash.net.URLRequest; 6 | import flash.system.System; 7 | import flash.ui.*; 8 | import flash.utils.getTimer; 9 | import flash.net.navigateToURL 10 | 11 | /** 12 | * 右键菜单中加入profiler选项,点击能看到帧频监控 13 | * 用法:SWFProfiler.init(stage, this); 14 | * @author shanem 15 | */ 16 | public class SWFProfiler { 17 | private static var itvTime : int; 18 | private static var initTime : int; 19 | private static var currentTime : int; 20 | private static var frameCount : int; 21 | private static var totalCount : int; 22 | 23 | public static var minFps : Number; 24 | public static var maxFps : Number; 25 | public static var minMem : Number; 26 | public static var maxMem : Number; 27 | public static var history : int = 60; 28 | public static var fpsList : Array = []; 29 | public static var memList : Array = []; 30 | 31 | private static var displayed : Boolean = false; 32 | private static var started : Boolean = false; 33 | private static var inited : Boolean = false; 34 | private static var frame : Sprite; 35 | private static var stage : Stage; 36 | private static var content : ProfilerContent; 37 | private static var ci : ContextMenuItem; 38 | private static var ryanMenuItem:ContextMenuItem 39 | private static var cm : ContextMenu 40 | 41 | public static function init(swf : Stage, context : InteractiveObject) : void { 42 | if(inited) return; 43 | 44 | inited = true; 45 | stage = swf; 46 | 47 | 48 | content = new ProfilerContent(); 49 | frame = new Sprite(); 50 | 51 | 52 | minFps = Number.MAX_VALUE; 53 | maxFps = Number.MIN_VALUE; 54 | minMem = Number.MAX_VALUE; 55 | maxMem = Number.MIN_VALUE; 56 | 57 | cm = new ContextMenu(); 58 | cm.hideBuiltInItems(); 59 | ci = new ContextMenuItem("Show Profiler"); 60 | addEvent(ci, ContextMenuEvent.MENU_ITEM_SELECT, onSelect); 61 | cm.customItems = [ci,ryanMenuItem]; 62 | context.contextMenu = cm 63 | start(); 64 | } 65 | 66 | public static function start() : void { 67 | if(started) return; 68 | 69 | started = true; 70 | initTime = itvTime = getTimer(); 71 | totalCount = frameCount = 0; 72 | 73 | addEvent(frame, Event.ENTER_FRAME, draw); 74 | } 75 | 76 | public static function stop() : void { 77 | if(!started) return; 78 | 79 | started = false; 80 | 81 | removeEvent(frame, Event.ENTER_FRAME, draw); 82 | } 83 | 84 | public static function gc() : void { 85 | try { 86 | new LocalConnection().connect('foo'); 87 | new LocalConnection().connect('foo'); 88 | } catch (e : Error) { 89 | } 90 | } 91 | 92 | public static function get currentFps() : Number { 93 | return frameCount / intervalTime; 94 | } 95 | 96 | public static function get currentMem() : Number { 97 | return (System.totalMemory / 1024) / 1000; 98 | } 99 | 100 | public static function get averageFps() : Number { 101 | return totalCount / runningTime; 102 | } 103 | 104 | private static function get runningTime() : Number { 105 | return (currentTime - initTime) / 1000; 106 | } 107 | 108 | private static function get intervalTime() : Number { 109 | return (currentTime - itvTime) / 1000; 110 | } 111 | 112 | 113 | private static function onSelect(e : ContextMenuEvent) : void { 114 | if(!displayed) { 115 | show(); 116 | } else { 117 | hide(); 118 | } 119 | } 120 | 121 | private static function onRyanSelect(e:ContextMenuEvent):void { 122 | navigateToURL(new URLRequest('http://www.ryan-liu.com/blog'),'_blank') 123 | } 124 | 125 | private static function show() : void { 126 | ci.caption = "Hide Profiler"; 127 | displayed = true; 128 | addEvent(stage, Event.RESIZE, resize); 129 | stage.addChild(content); 130 | updateDisplay(); 131 | } 132 | 133 | private static function hide() : void { 134 | ci.caption = "Show Profiler"; 135 | displayed = false; 136 | removeEvent(stage, Event.RESIZE, resize); 137 | stage.removeChild(content); 138 | } 139 | 140 | private static function resize(e:Event) : void { 141 | content.update(runningTime, minFps, maxFps, minMem, maxMem, currentFps, currentMem, averageFps, fpsList, memList, history); 142 | } 143 | 144 | private static function draw(e : Event) : void { 145 | currentTime = getTimer(); 146 | 147 | frameCount++; 148 | totalCount++; 149 | 150 | if(intervalTime >= 1) { 151 | if(displayed) { 152 | updateDisplay(); 153 | } else { 154 | updateMinMax(); 155 | } 156 | 157 | fpsList.unshift(currentFps); 158 | memList.unshift(currentMem); 159 | 160 | if(fpsList.length > history) fpsList.pop(); 161 | if(memList.length > history) memList.pop(); 162 | 163 | itvTime = currentTime; 164 | frameCount = 0; 165 | } 166 | } 167 | 168 | private static function updateDisplay() : void { 169 | updateMinMax(); 170 | content.update(runningTime, minFps, maxFps, minMem, maxMem, currentFps, currentMem, averageFps, fpsList, memList, history); 171 | } 172 | 173 | private static function updateMinMax() : void { 174 | minFps = Math.min(currentFps, minFps); 175 | maxFps = Math.max(currentFps, maxFps); 176 | 177 | minMem = Math.min(currentMem, minMem); 178 | maxMem = Math.max(currentMem, maxMem); 179 | } 180 | 181 | private static function addEvent(item : EventDispatcher, type : String, listener : Function) : void { 182 | item.addEventListener(type, listener, false, 0, true); 183 | } 184 | 185 | private static function removeEvent(item : EventDispatcher, type : String, listener : Function) : void { 186 | item.removeEventListener(type, listener); 187 | } 188 | } 189 | } 190 | 191 | import flash.display.*; 192 | import flash.events.Event; 193 | import flash.text.*; 194 | 195 | internal class ProfilerContent extends Sprite { 196 | 197 | private var minFpsTxtBx : TextField; 198 | private var maxFpsTxtBx : TextField; 199 | private var minMemTxtBx : TextField; 200 | private var maxMemTxtBx : TextField; 201 | private var infoTxtBx : TextField; 202 | private var box : Shape; 203 | private var fps : Shape; 204 | private var mb : Shape; 205 | 206 | public function ProfilerContent() : void { 207 | fps = new Shape(); 208 | mb = new Shape(); 209 | box = new Shape(); 210 | 211 | this.mouseChildren = false; 212 | this.mouseEnabled = false; 213 | 214 | fps.x = 65; 215 | fps.y = 45; 216 | mb.x = 65; 217 | mb.y = 90; 218 | 219 | var tf : TextFormat = new TextFormat("_sans", 9, 0xAAAAAA); 220 | 221 | infoTxtBx = new TextField(); 222 | infoTxtBx.autoSize = TextFieldAutoSize.LEFT; 223 | infoTxtBx.defaultTextFormat = new TextFormat("_sans", 11, 0xCCCCCC); 224 | infoTxtBx.y = 98; 225 | 226 | minFpsTxtBx = new TextField(); 227 | minFpsTxtBx.autoSize = TextFieldAutoSize.LEFT; 228 | minFpsTxtBx.defaultTextFormat = tf; 229 | minFpsTxtBx.x = 7; 230 | minFpsTxtBx.y = 37; 231 | 232 | maxFpsTxtBx = new TextField(); 233 | maxFpsTxtBx.autoSize = TextFieldAutoSize.LEFT; 234 | maxFpsTxtBx.defaultTextFormat = tf; 235 | maxFpsTxtBx.x = 7; 236 | maxFpsTxtBx.y = 5; 237 | 238 | minMemTxtBx = new TextField(); 239 | minMemTxtBx.autoSize = TextFieldAutoSize.LEFT; 240 | minMemTxtBx.defaultTextFormat = tf; 241 | minMemTxtBx.x = 7; 242 | minMemTxtBx.y = 83; 243 | 244 | maxMemTxtBx = new TextField(); 245 | maxMemTxtBx.autoSize = TextFieldAutoSize.LEFT; 246 | maxMemTxtBx.defaultTextFormat = tf; 247 | maxMemTxtBx.x = 7; 248 | maxMemTxtBx.y = 50; 249 | 250 | addChild(box); 251 | addChild(infoTxtBx); 252 | addChild(minFpsTxtBx); 253 | addChild(maxFpsTxtBx); 254 | addChild(minMemTxtBx); 255 | addChild(maxMemTxtBx); 256 | addChild(fps); 257 | addChild(mb); 258 | 259 | this.addEventListener(Event.ADDED_TO_STAGE, added, false, 0, true); 260 | this.addEventListener(Event.REMOVED_FROM_STAGE, removed, false, 0, true); 261 | } 262 | 263 | public function update(runningTime : Number, minFps : Number, maxFps : Number, minMem : Number, maxMem : Number, currentFps : Number, currentMem : Number, averageFps : Number, fpsList : Array, memList : Array, history : int) : void { 264 | if(runningTime >= 1) { 265 | minFpsTxtBx.text = minFps.toFixed(3) + " Fps"; 266 | maxFpsTxtBx.text = maxFps.toFixed(3) + " Fps"; 267 | minMemTxtBx.text = minMem.toFixed(3) + " Mb"; 268 | maxMemTxtBx.text = maxMem.toFixed(3) + " Mb"; 269 | } 270 | 271 | infoTxtBx.text = "Current Fps " + currentFps.toFixed(3) + " | Average Fps " + averageFps.toFixed(3) + " | Memory Used " + currentMem.toFixed(3) + " Mb"; 272 | infoTxtBx.x = stage.stageWidth - infoTxtBx.width - 20; 273 | 274 | var vec : Graphics = fps.graphics; 275 | vec.clear(); 276 | vec.lineStyle(1, 0x33FF00, 0.7); 277 | 278 | var i : int = 0; 279 | var len : int = fpsList.length; 280 | var height : int = 35; 281 | var width : int = stage.stageWidth - 80; 282 | var inc : Number = width / (history - 1); 283 | var rateRange : Number = maxFps - minFps; 284 | var value : Number; 285 | 286 | for(i = 0;i < len; i++) { 287 | value = (fpsList[i] - minFps) / rateRange; 288 | if(i == 0) { 289 | vec.moveTo(0, -value * height); 290 | } else { 291 | vec.lineTo(i * inc, -value * height); 292 | } 293 | } 294 | 295 | vec = mb.graphics; 296 | vec.clear(); 297 | vec.lineStyle(1, 0x0066FF, 0.7); 298 | 299 | i = 0; 300 | len = memList.length; 301 | rateRange = maxMem - minMem; 302 | for(i = 0;i < len; i++) { 303 | value = (memList[i] - minMem) / rateRange; 304 | if(i == 0) { 305 | vec.moveTo(0, -value * height); 306 | } else { 307 | vec.lineTo(i * inc, -value * height); 308 | } 309 | } 310 | } 311 | 312 | private function added(e : Event) : void { 313 | resize(); 314 | stage.addEventListener(Event.RESIZE, resize, false, 0, true); 315 | } 316 | 317 | private function removed(e : Event) : void { 318 | stage.removeEventListener(Event.RESIZE, resize); 319 | } 320 | 321 | private function resize(e : Event = null) : void { 322 | var vec : Graphics = box.graphics; 323 | vec.clear(); 324 | 325 | vec.beginFill(0x000000, 0.5); 326 | vec.drawRect(0, 0, stage.stageWidth, 120); 327 | vec.lineStyle(1, 0xFFFFFF, 0.2); 328 | 329 | vec.moveTo(65, 45); 330 | vec.lineTo(65, 10); 331 | vec.moveTo(65, 45); 332 | vec.lineTo(stage.stageWidth - 15, 45); 333 | 334 | vec.moveTo(65, 90); 335 | vec.lineTo(65, 55); 336 | vec.moveTo(65, 90); 337 | vec.lineTo(stage.stageWidth - 15, 90); 338 | 339 | vec.endFill(); 340 | 341 | infoTxtBx.x = stage.stageWidth - infoTxtBx.width - 20; 342 | } 343 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONEncoder.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | import flash.utils.describeType; 37 | 38 | public class JSONEncoder 39 | { 40 | 41 | /** The string that is going to represent the object we're encoding */ 42 | private var jsonString:String; 43 | 44 | /** 45 | * Creates a new JSONEncoder. 46 | * 47 | * @param o The object to encode as a JSON string 48 | * @langversion ActionScript 3.0 49 | * @playerversion Flash 9.0 50 | * @tiptext 51 | */ 52 | public function JSONEncoder( value:* ) 53 | { 54 | jsonString = convertToString( value ); 55 | } 56 | 57 | /** 58 | * Gets the JSON string from the encoder. 59 | * 60 | * @return The JSON string representation of the object 61 | * that was passed to the constructor 62 | * @langversion ActionScript 3.0 63 | * @playerversion Flash 9.0 64 | * @tiptext 65 | */ 66 | public function getString():String 67 | { 68 | return jsonString; 69 | } 70 | 71 | /** 72 | * Converts a value to it's JSON string equivalent. 73 | * 74 | * @param value The value to convert. Could be any 75 | * type (object, number, array, etc) 76 | */ 77 | private function convertToString( value:* ):String 78 | { 79 | // determine what value is and convert it based on it's type 80 | if ( value is String ) 81 | { 82 | // escape the string so it's formatted correctly 83 | return escapeString( value as String ); 84 | } 85 | else if ( value is Number ) 86 | { 87 | // only encode numbers that finate 88 | return isFinite( value as Number ) ? value.toString() : "null"; 89 | } 90 | else if ( value is Boolean ) 91 | { 92 | // convert boolean to string easily 93 | return value ? "true" : "false"; 94 | } 95 | else if ( value is Array ) 96 | { 97 | // call the helper method to convert an array 98 | return arrayToString( value as Array ); 99 | } 100 | else if ( value is Object && value != null ) 101 | { 102 | // call the helper method to convert an object 103 | return objectToString( value ); 104 | } 105 | 106 | return "null"; 107 | } 108 | 109 | /** 110 | * Escapes a string accoding to the JSON specification. 111 | * 112 | * @param str The string to be escaped 113 | * @return The string with escaped special characters 114 | * according to the JSON specification 115 | */ 116 | private function escapeString( str:String ):String 117 | { 118 | // create a string to store the string's jsonstring value 119 | var s:String = ""; 120 | // current character in the string we're processing 121 | var ch:String; 122 | // store the length in a local variable to reduce lookups 123 | var len:Number = str.length; 124 | 125 | // loop over all of the characters in the string 126 | for ( var i:int = 0; i < len; i++ ) 127 | { 128 | // examine the character to determine if we have to escape it 129 | ch = str.charAt( i ); 130 | switch ( ch ) 131 | { 132 | case '"': // quotation mark 133 | s += "\\\""; 134 | break; 135 | 136 | //case '/': // solidus 137 | // s += "\\/"; 138 | // break; 139 | 140 | case '\\': // reverse solidus 141 | s += "\\\\"; 142 | break; 143 | 144 | case '\b': // bell 145 | s += "\\b"; 146 | break; 147 | 148 | case '\f': // form feed 149 | s += "\\f"; 150 | break; 151 | 152 | case '\n': // newline 153 | s += "\\n"; 154 | break; 155 | 156 | case '\r': // carriage return 157 | s += "\\r"; 158 | break; 159 | 160 | case '\t': // horizontal tab 161 | s += "\\t"; 162 | break; 163 | 164 | default: // everything else 165 | 166 | // check for a control character and escape as unicode 167 | if ( ch < ' ' ) 168 | { 169 | // get the hex digit(s) of the character (either 1 or 2 digits) 170 | var hexCode:String = ch.charCodeAt( 0 ).toString( 16 ); 171 | 172 | // ensure that there are 4 digits by adjusting 173 | // the # of zeros accordingly. 174 | var zeroPad:String = hexCode.length == 2 ? "00" : "000"; 175 | 176 | // create the unicode escape sequence with 4 hex digits 177 | s += "\\u" + zeroPad + hexCode; 178 | } 179 | else 180 | { 181 | 182 | // no need to do any special encoding, just pass-through 183 | s += ch; 184 | 185 | } 186 | } // end switch 187 | 188 | } // end for loop 189 | 190 | return "\"" + s + "\""; 191 | } 192 | 193 | /** 194 | * Converts an array to it's JSON string equivalent 195 | * 196 | * @param a The array to convert 197 | * @return The JSON string representation of a 198 | */ 199 | private function arrayToString( a:Array ):String 200 | { 201 | // create a string to store the array's jsonstring value 202 | var s:String = ""; 203 | 204 | // loop over the elements in the array and add their converted 205 | // values to the string 206 | var length:int = a.length; 207 | for ( var i:int = 0; i < length; i++ ) 208 | { 209 | // when the length is 0 we're adding the first element so 210 | // no comma is necessary 211 | if ( s.length > 0 ) 212 | { 213 | // we've already added an element, so add the comma separator 214 | s += "," 215 | } 216 | 217 | // convert the value to a string 218 | s += convertToString( a[ i ] ); 219 | } 220 | 221 | // KNOWN ISSUE: In ActionScript, Arrays can also be associative 222 | // objects and you can put anything in them, ie: 223 | // myArray["foo"] = "bar"; 224 | // 225 | // These properties aren't picked up in the for loop above because 226 | // the properties don't correspond to indexes. However, we're 227 | // sort of out luck because the JSON specification doesn't allow 228 | // these types of array properties. 229 | // 230 | // So, if the array was also used as an associative object, there 231 | // may be some values in the array that don't get properly encoded. 232 | // 233 | // A possible solution is to instead encode the Array as an Object 234 | // but then it won't get decoded correctly (and won't be an 235 | // Array instance) 236 | 237 | // close the array and return it's string value 238 | return "[" + s + "]"; 239 | } 240 | 241 | /** 242 | * Converts an object to it's JSON string equivalent 243 | * 244 | * @param o The object to convert 245 | * @return The JSON string representation of o 246 | */ 247 | private function objectToString( o:Object ):String 248 | { 249 | // create a string to store the object's jsonstring value 250 | var s:String = ""; 251 | 252 | // determine if o is a class instance or a plain object 253 | var classInfo:XML = describeType( o ); 254 | if ( classInfo.@name.toString() == "Object" ) 255 | { 256 | // the value of o[key] in the loop below - store this 257 | // as a variable so we don't have to keep looking up o[key] 258 | // when testing for valid values to convert 259 | var value:Object; 260 | 261 | // loop over the keys in the object and add their converted 262 | // values to the string 263 | for ( var key:String in o ) 264 | { 265 | // assign value to a variable for quick lookup 266 | value = o[ key ]; 267 | 268 | // don't add function's to the JSON string 269 | if ( value is Function ) 270 | { 271 | // skip this key and try another 272 | continue; 273 | } 274 | 275 | // when the length is 0 we're adding the first item so 276 | // no comma is necessary 277 | if ( s.length > 0 ) 278 | { 279 | // we've already added an item, so add the comma separator 280 | s += "," 281 | } 282 | 283 | s += escapeString( key ) + ":" + convertToString( value ); 284 | } 285 | } 286 | else // o is a class instance 287 | { 288 | // Loop over all of the variables and accessors in the class and 289 | // serialize them along with their values. 290 | for each ( var v:XML in classInfo..*.( 291 | name() == "variable" 292 | || 293 | ( 294 | name() == "accessor" 295 | // Issue #116 - Make sure accessors are readable 296 | && attribute( "access" ).charAt( 0 ) == "r" ) 297 | ) ) 298 | { 299 | // Issue #110 - If [Transient] metadata exists, then we should skip 300 | if ( v.metadata && v.metadata.( @name == "Transient" ).length() > 0 ) 301 | { 302 | continue; 303 | } 304 | 305 | // When the length is 0 we're adding the first item so 306 | // no comma is necessary 307 | if ( s.length > 0 ) 308 | { 309 | // We've already added an item, so add the comma separator 310 | s += "," 311 | } 312 | 313 | s += escapeString( v.@name.toString() ) + ":" 314 | + convertToString( o[ v.@name ] ); 315 | } 316 | } 317 | 318 | return "{" + s + "}"; 319 | } 320 | 321 | } 322 | 323 | } -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONDecoder.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | public class JSONDecoder 37 | { 38 | 39 | /** 40 | * Flag indicating if the parser should be strict about the format 41 | * of the JSON string it is attempting to decode. 42 | */ 43 | private var strict:Boolean; 44 | 45 | /** The value that will get parsed from the JSON string */ 46 | private var value:*; 47 | 48 | /** The tokenizer designated to read the JSON string */ 49 | private var tokenizer:JSONTokenizer; 50 | 51 | /** The current token from the tokenizer */ 52 | private var token:JSONToken; 53 | 54 | /** 55 | * Constructs a new JSONDecoder to parse a JSON string 56 | * into a native object. 57 | * 58 | * @param s The JSON string to be converted 59 | * into a native object 60 | * @param strict Flag indicating if the JSON string needs to 61 | * strictly match the JSON standard or not. 62 | * @langversion ActionScript 3.0 63 | * @playerversion Flash 9.0 64 | * @tiptext 65 | */ 66 | public function JSONDecoder( s:String, strict:Boolean ) 67 | { 68 | this.strict = strict; 69 | tokenizer = new JSONTokenizer( s, strict ); 70 | 71 | nextToken(); 72 | value = parseValue(); 73 | 74 | // Make sure the input stream is empty 75 | if ( strict && nextToken() != null ) 76 | { 77 | tokenizer.parseError( "Unexpected characters left in input stream" ); 78 | } 79 | } 80 | 81 | /** 82 | * Gets the internal object that was created by parsing 83 | * the JSON string passed to the constructor. 84 | * 85 | * @return The internal object representation of the JSON 86 | * string that was passed to the constructor 87 | * @langversion ActionScript 3.0 88 | * @playerversion Flash 9.0 89 | * @tiptext 90 | */ 91 | public function getValue():* 92 | { 93 | return value; 94 | } 95 | 96 | /** 97 | * Returns the next token from the tokenzier reading 98 | * the JSON string 99 | */ 100 | private final function nextToken():JSONToken 101 | { 102 | return token = tokenizer.getNextToken(); 103 | } 104 | 105 | /** 106 | * Returns the next token from the tokenizer reading 107 | * the JSON string and verifies that the token is valid. 108 | */ 109 | private final function nextValidToken():JSONToken 110 | { 111 | token = tokenizer.getNextToken(); 112 | checkValidToken(); 113 | 114 | return token; 115 | } 116 | 117 | /** 118 | * Verifies that the token is valid. 119 | */ 120 | private final function checkValidToken():void 121 | { 122 | // Catch errors when the input stream ends abruptly 123 | if ( token == null ) 124 | { 125 | tokenizer.parseError( "Unexpected end of input" ); 126 | } 127 | } 128 | 129 | /** 130 | * Attempt to parse an array. 131 | */ 132 | private final function parseArray():Array 133 | { 134 | // create an array internally that we're going to attempt 135 | // to parse from the tokenizer 136 | var a:Array = new Array(); 137 | 138 | // grab the next token from the tokenizer to move 139 | // past the opening [ 140 | nextValidToken(); 141 | 142 | // check to see if we have an empty array 143 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 144 | { 145 | // we're done reading the array, so return it 146 | return a; 147 | } 148 | // in non-strict mode an empty array is also a comma 149 | // followed by a right bracket 150 | else if ( !strict && token.type == JSONTokenType.COMMA ) 151 | { 152 | // move past the comma 153 | nextValidToken(); 154 | 155 | // check to see if we're reached the end of the array 156 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 157 | { 158 | return a; 159 | } 160 | else 161 | { 162 | tokenizer.parseError( "Leading commas are not supported. Expecting ']' but found " + token.value ); 163 | } 164 | } 165 | 166 | // deal with elements of the array, and use an "infinite" 167 | // loop because we could have any amount of elements 168 | while ( true ) 169 | { 170 | // read in the value and add it to the array 171 | a.push( parseValue() ); 172 | 173 | // after the value there should be a ] or a , 174 | nextValidToken(); 175 | 176 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 177 | { 178 | // we're done reading the array, so return it 179 | return a; 180 | } 181 | else if ( token.type == JSONTokenType.COMMA ) 182 | { 183 | // move past the comma and read another value 184 | nextToken(); 185 | 186 | // Allow arrays to have a comma after the last element 187 | // if the decoder is not in strict mode 188 | if ( !strict ) 189 | { 190 | checkValidToken(); 191 | 192 | // Reached ",]" as the end of the array, so return it 193 | if ( token.type == JSONTokenType.RIGHT_BRACKET ) 194 | { 195 | return a; 196 | } 197 | } 198 | } 199 | else 200 | { 201 | tokenizer.parseError( "Expecting ] or , but found " + token.value ); 202 | } 203 | } 204 | 205 | return null; 206 | } 207 | 208 | /** 209 | * Attempt to parse an object. 210 | */ 211 | private final function parseObject():Object 212 | { 213 | // create the object internally that we're going to 214 | // attempt to parse from the tokenizer 215 | var o:Object = new Object(); 216 | 217 | // store the string part of an object member so 218 | // that we can assign it a value in the object 219 | var key:String 220 | 221 | // grab the next token from the tokenizer 222 | nextValidToken(); 223 | 224 | // check to see if we have an empty object 225 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 226 | { 227 | // we're done reading the object, so return it 228 | return o; 229 | } 230 | // in non-strict mode an empty object is also a comma 231 | // followed by a right bracket 232 | else if ( !strict && token.type == JSONTokenType.COMMA ) 233 | { 234 | // move past the comma 235 | nextValidToken(); 236 | 237 | // check to see if we're reached the end of the object 238 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 239 | { 240 | return o; 241 | } 242 | else 243 | { 244 | tokenizer.parseError( "Leading commas are not supported. Expecting '}' but found " + token.value ); 245 | } 246 | } 247 | 248 | // deal with members of the object, and use an "infinite" 249 | // loop because we could have any amount of members 250 | while ( true ) 251 | { 252 | if ( token.type == JSONTokenType.STRING ) 253 | { 254 | // the string value we read is the key for the object 255 | key = String( token.value ); 256 | 257 | // move past the string to see what's next 258 | nextValidToken(); 259 | 260 | // after the string there should be a : 261 | if ( token.type == JSONTokenType.COLON ) 262 | { 263 | // move past the : and read/assign a value for the key 264 | nextToken(); 265 | o[ key ] = parseValue(); 266 | 267 | // move past the value to see what's next 268 | nextValidToken(); 269 | 270 | // after the value there's either a } or a , 271 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 272 | { 273 | // we're done reading the object, so return it 274 | return o; 275 | } 276 | else if ( token.type == JSONTokenType.COMMA ) 277 | { 278 | // skip past the comma and read another member 279 | nextToken(); 280 | 281 | // Allow objects to have a comma after the last member 282 | // if the decoder is not in strict mode 283 | if ( !strict ) 284 | { 285 | checkValidToken(); 286 | 287 | // Reached ",}" as the end of the object, so return it 288 | if ( token.type == JSONTokenType.RIGHT_BRACE ) 289 | { 290 | return o; 291 | } 292 | } 293 | } 294 | else 295 | { 296 | tokenizer.parseError( "Expecting } or , but found " + token.value ); 297 | } 298 | } 299 | else 300 | { 301 | tokenizer.parseError( "Expecting : but found " + token.value ); 302 | } 303 | } 304 | else 305 | { 306 | tokenizer.parseError( "Expecting string but found " + token.value ); 307 | } 308 | } 309 | return null; 310 | } 311 | 312 | /** 313 | * Attempt to parse a value 314 | */ 315 | private final function parseValue():Object 316 | { 317 | checkValidToken(); 318 | 319 | switch ( token.type ) 320 | { 321 | case JSONTokenType.LEFT_BRACE: 322 | return parseObject(); 323 | 324 | case JSONTokenType.LEFT_BRACKET: 325 | return parseArray(); 326 | 327 | case JSONTokenType.STRING: 328 | case JSONTokenType.NUMBER: 329 | case JSONTokenType.TRUE: 330 | case JSONTokenType.FALSE: 331 | case JSONTokenType.NULL: 332 | return token.value; 333 | 334 | case JSONTokenType.NAN: 335 | if ( !strict ) 336 | { 337 | return token.value; 338 | } 339 | else 340 | { 341 | tokenizer.parseError( "Unexpected " + token.value ); 342 | } 343 | 344 | default: 345 | tokenizer.parseError( "Unexpected " + token.value ); 346 | 347 | } 348 | 349 | return null; 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/com/adobe/serialization/json/JSONTokenizer.as: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | package com.adobe.serialization.json 34 | { 35 | 36 | public class JSONTokenizer 37 | { 38 | 39 | /** 40 | * Flag indicating if the tokenizer should only recognize 41 | * standard JSON tokens. Setting to false allows 42 | * tokens such as NaN and allows numbers to be formatted as 43 | * hex, etc. 44 | */ 45 | private var strict:Boolean; 46 | 47 | /** The object that will get parsed from the JSON string */ 48 | private var obj:Object; 49 | 50 | /** The JSON string to be parsed */ 51 | private var jsonString:String; 52 | 53 | /** The current parsing location in the JSON string */ 54 | private var loc:int; 55 | 56 | /** The current character in the JSON string during parsing */ 57 | private var ch:String; 58 | 59 | /** 60 | * The regular expression used to make sure the string does not 61 | * contain invalid control characters. 62 | */ 63 | private const controlCharsRegExp:RegExp = /[\x00-\x1F]/; 64 | 65 | /** 66 | * Constructs a new JSONDecoder to parse a JSON string 67 | * into a native object. 68 | * 69 | * @param s The JSON string to be converted 70 | * into a native object 71 | */ 72 | public function JSONTokenizer( s:String, strict:Boolean ) 73 | { 74 | jsonString = s; 75 | this.strict = strict; 76 | loc = 0; 77 | 78 | // prime the pump by getting the first character 79 | nextChar(); 80 | } 81 | 82 | /** 83 | * Gets the next token in the input sting and advances 84 | * the character to the next character after the token 85 | */ 86 | public function getNextToken():JSONToken 87 | { 88 | var token:JSONToken = null; 89 | 90 | // skip any whitespace / comments since the last 91 | // token was read 92 | skipIgnored(); 93 | 94 | // examine the new character and see what we have... 95 | switch ( ch ) 96 | { 97 | case '{': 98 | token = JSONToken.create( JSONTokenType.LEFT_BRACE, ch ); 99 | nextChar(); 100 | break 101 | 102 | case '}': 103 | token = JSONToken.create( JSONTokenType.RIGHT_BRACE, ch ); 104 | nextChar(); 105 | break 106 | 107 | case '[': 108 | token = JSONToken.create( JSONTokenType.LEFT_BRACKET, ch ); 109 | nextChar(); 110 | break 111 | 112 | case ']': 113 | token = JSONToken.create( JSONTokenType.RIGHT_BRACKET, ch ); 114 | nextChar(); 115 | break 116 | 117 | case ',': 118 | token = JSONToken.create( JSONTokenType.COMMA, ch ); 119 | nextChar(); 120 | break 121 | 122 | case ':': 123 | token = JSONToken.create( JSONTokenType.COLON, ch ); 124 | nextChar(); 125 | break; 126 | 127 | case 't': // attempt to read true 128 | var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar(); 129 | 130 | if ( possibleTrue == "true" ) 131 | { 132 | token = JSONToken.create( JSONTokenType.TRUE, true ); 133 | nextChar(); 134 | } 135 | else 136 | { 137 | parseError( "Expecting 'true' but found " + possibleTrue ); 138 | } 139 | 140 | break; 141 | 142 | case 'f': // attempt to read false 143 | var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar(); 144 | 145 | if ( possibleFalse == "false" ) 146 | { 147 | token = JSONToken.create( JSONTokenType.FALSE, false ); 148 | nextChar(); 149 | } 150 | else 151 | { 152 | parseError( "Expecting 'false' but found " + possibleFalse ); 153 | } 154 | 155 | break; 156 | 157 | case 'n': // attempt to read null 158 | var possibleNull:String = "n" + nextChar() + nextChar() + nextChar(); 159 | 160 | if ( possibleNull == "null" ) 161 | { 162 | token = JSONToken.create( JSONTokenType.NULL, null ); 163 | nextChar(); 164 | } 165 | else 166 | { 167 | parseError( "Expecting 'null' but found " + possibleNull ); 168 | } 169 | 170 | break; 171 | 172 | case 'N': // attempt to read NaN 173 | var possibleNaN:String = "N" + nextChar() + nextChar(); 174 | 175 | if ( possibleNaN == "NaN" ) 176 | { 177 | token = JSONToken.create( JSONTokenType.NAN, NaN ); 178 | nextChar(); 179 | } 180 | else 181 | { 182 | parseError( "Expecting 'NaN' but found " + possibleNaN ); 183 | } 184 | 185 | break; 186 | 187 | case '"': // the start of a string 188 | token = readString(); 189 | break; 190 | 191 | default: 192 | // see if we can read a number 193 | if ( isDigit( ch ) || ch == '-' ) 194 | { 195 | token = readNumber(); 196 | } 197 | else if ( ch == '' ) 198 | { 199 | // check for reading past the end of the string 200 | token = null; 201 | } 202 | else 203 | { 204 | // not sure what was in the input string - it's not 205 | // anything we expected 206 | parseError( "Unexpected " + ch + " encountered" ); 207 | } 208 | } 209 | 210 | return token; 211 | } 212 | 213 | /** 214 | * Attempts to read a string from the input string. Places 215 | * the character location at the first character after the 216 | * string. It is assumed that ch is " before this method is called. 217 | * 218 | * @return the JSONToken with the string value if a string could 219 | * be read. Throws an error otherwise. 220 | */ 221 | private final function readString():JSONToken 222 | { 223 | // Rather than examine the string character-by-character, it's 224 | // faster to use indexOf to try to and find the closing quote character 225 | // and then replace escape sequences after the fact. 226 | 227 | // Start at the current input stream position 228 | var quoteIndex:int = loc; 229 | do 230 | { 231 | // Find the next quote in the input stream 232 | quoteIndex = jsonString.indexOf( "\"", quoteIndex ); 233 | 234 | if ( quoteIndex >= 0 ) 235 | { 236 | // We found the next double quote character in the string, but we need 237 | // to make sure it is not part of an escape sequence. 238 | 239 | // Keep looping backwards while the previous character is a backslash 240 | var backspaceCount:int = 0; 241 | var backspaceIndex:int = quoteIndex - 1; 242 | while ( jsonString.charAt( backspaceIndex ) == "\\" ) 243 | { 244 | backspaceCount++; 245 | backspaceIndex--; 246 | } 247 | 248 | // If we have an even number of backslashes, that means this is the ending quote 249 | if ( ( backspaceCount & 1 ) == 0 ) 250 | { 251 | break; 252 | } 253 | 254 | // At this point, the quote was determined to be part of an escape sequence 255 | // so we need to move past the quote index to look for the next one 256 | quoteIndex++; 257 | } 258 | else // There are no more quotes in the string and we haven't found the end yet 259 | { 260 | parseError( "Unterminated string literal" ); 261 | } 262 | } while ( true ); 263 | 264 | // Unescape the string 265 | // the token for the string we'll try to read 266 | var token:JSONToken = JSONToken.create( 267 | JSONTokenType.STRING, 268 | // Attach resulting string to the token to return it 269 | unescapeString( jsonString.substr( loc, quoteIndex - loc ) ) ); 270 | 271 | // Move past the closing quote in the input string. This updates the next 272 | // character in the input stream to be the character one after the closing quote 273 | loc = quoteIndex + 1; 274 | nextChar(); 275 | 276 | return token; 277 | } 278 | 279 | /** 280 | * Convert all JavaScript escape characters into normal characters 281 | * 282 | * @param input The input string to convert 283 | * @return Original string with escape characters replaced by real characters 284 | */ 285 | public function unescapeString( input:String ):String 286 | { 287 | // Issue #104 - If the string contains any unescaped control characters, this 288 | // is an error in strict mode 289 | if ( strict && controlCharsRegExp.test( input ) ) 290 | { 291 | parseError( "String contains unescaped control character (0x00-0x1F)" ); 292 | } 293 | 294 | var result:String = ""; 295 | var backslashIndex:int = 0; 296 | var nextSubstringStartPosition:int = 0; 297 | var len:int = input.length; 298 | do 299 | { 300 | // Find the next backslash in the input 301 | backslashIndex = input.indexOf( '\\', nextSubstringStartPosition ); 302 | 303 | if ( backslashIndex >= 0 ) 304 | { 305 | result += input.substr( nextSubstringStartPosition, backslashIndex - nextSubstringStartPosition ); 306 | 307 | // Move past the backslash and next character (all escape sequences are 308 | // two characters, except for \u, which will advance this further) 309 | nextSubstringStartPosition = backslashIndex + 2; 310 | 311 | // Check the next character so we know what to escape 312 | var escapedChar:String = input.charAt( backslashIndex + 1 ); 313 | switch ( escapedChar ) 314 | { 315 | // Try to list the most common expected cases first to improve performance 316 | 317 | case '"': 318 | result += escapedChar; 319 | break; // quotation mark 320 | case '\\': 321 | result += escapedChar; 322 | break; // reverse solidus 323 | case 'n': 324 | result += '\n'; 325 | break; // newline 326 | case 'r': 327 | result += '\r'; 328 | break; // carriage return 329 | case 't': 330 | result += '\t'; 331 | break; // horizontal tab 332 | 333 | // Convert a unicode escape sequence to it's character value 334 | case 'u': 335 | 336 | // Save the characters as a string we'll convert to an int 337 | var hexValue:String = ""; 338 | 339 | var unicodeEndPosition:int = nextSubstringStartPosition + 4; 340 | 341 | // Make sure there are enough characters in the string leftover 342 | if ( unicodeEndPosition > len ) 343 | { 344 | parseError( "Unexpected end of input. Expecting 4 hex digits after \\u." ); 345 | } 346 | 347 | // Try to find 4 hex characters 348 | for ( var i:int = nextSubstringStartPosition; i < unicodeEndPosition; i++ ) 349 | { 350 | // get the next character and determine 351 | // if it's a valid hex digit or not 352 | var possibleHexChar:String = input.charAt( i ); 353 | if ( !isHexDigit( possibleHexChar ) ) 354 | { 355 | parseError( "Excepted a hex digit, but found: " + possibleHexChar ); 356 | } 357 | 358 | // Valid hex digit, add it to the value 359 | hexValue += possibleHexChar; 360 | } 361 | 362 | // Convert hexValue to an integer, and use that 363 | // integer value to create a character to add 364 | // to our string. 365 | result += String.fromCharCode( parseInt( hexValue, 16 ) ); 366 | 367 | // Move past the 4 hex digits that we just read 368 | nextSubstringStartPosition = unicodeEndPosition; 369 | break; 370 | 371 | case 'f': 372 | result += '\f'; 373 | break; // form feed 374 | case '/': 375 | result += '/'; 376 | break; // solidus 377 | case 'b': 378 | result += '\b'; 379 | break; // bell 380 | default: 381 | result += '\\' + escapedChar; // Couldn't unescape the sequence, so just pass it through 382 | } 383 | } 384 | else 385 | { 386 | // No more backslashes to replace, append the rest of the string 387 | result += input.substr( nextSubstringStartPosition ); 388 | break; 389 | } 390 | 391 | } while ( nextSubstringStartPosition < len ); 392 | 393 | return result; 394 | } 395 | 396 | /** 397 | * Attempts to read a number from the input string. Places 398 | * the character location at the first character after the 399 | * number. 400 | * 401 | * @return The JSONToken with the number value if a number could 402 | * be read. Throws an error otherwise. 403 | */ 404 | private final function readNumber():JSONToken 405 | { 406 | // the string to accumulate the number characters 407 | // into that we'll convert to a number at the end 408 | var input:String = ""; 409 | 410 | // check for a negative number 411 | if ( ch == '-' ) 412 | { 413 | input += '-'; 414 | nextChar(); 415 | } 416 | 417 | // the number must start with a digit 418 | if ( !isDigit( ch ) ) 419 | { 420 | parseError( "Expecting a digit" ); 421 | } 422 | 423 | // 0 can only be the first digit if it 424 | // is followed by a decimal point 425 | if ( ch == '0' ) 426 | { 427 | input += ch; 428 | nextChar(); 429 | 430 | // make sure no other digits come after 0 431 | if ( isDigit( ch ) ) 432 | { 433 | parseError( "A digit cannot immediately follow 0" ); 434 | } 435 | // unless we have 0x which starts a hex number, but this 436 | // doesn't match JSON spec so check for not strict mode. 437 | else if ( !strict && ch == 'x' ) 438 | { 439 | // include the x in the input 440 | input += ch; 441 | nextChar(); 442 | 443 | // need at least one hex digit after 0x to 444 | // be valid 445 | if ( isHexDigit( ch ) ) 446 | { 447 | input += ch; 448 | nextChar(); 449 | } 450 | else 451 | { 452 | parseError( "Number in hex format require at least one hex digit after \"0x\"" ); 453 | } 454 | 455 | // consume all of the hex values 456 | while ( isHexDigit( ch ) ) 457 | { 458 | input += ch; 459 | nextChar(); 460 | } 461 | } 462 | } 463 | else 464 | { 465 | // read numbers while we can 466 | while ( isDigit( ch ) ) 467 | { 468 | input += ch; 469 | nextChar(); 470 | } 471 | } 472 | 473 | // check for a decimal value 474 | if ( ch == '.' ) 475 | { 476 | input += '.'; 477 | nextChar(); 478 | 479 | // after the decimal there has to be a digit 480 | if ( !isDigit( ch ) ) 481 | { 482 | parseError( "Expecting a digit" ); 483 | } 484 | 485 | // read more numbers to get the decimal value 486 | while ( isDigit( ch ) ) 487 | { 488 | input += ch; 489 | nextChar(); 490 | } 491 | } 492 | 493 | // check for scientific notation 494 | if ( ch == 'e' || ch == 'E' ) 495 | { 496 | input += "e" 497 | nextChar(); 498 | // check for sign 499 | if ( ch == '+' || ch == '-' ) 500 | { 501 | input += ch; 502 | nextChar(); 503 | } 504 | 505 | // require at least one number for the exponent 506 | // in this case 507 | if ( !isDigit( ch ) ) 508 | { 509 | parseError( "Scientific notation number needs exponent value" ); 510 | } 511 | 512 | // read in the exponent 513 | while ( isDigit( ch ) ) 514 | { 515 | input += ch; 516 | nextChar(); 517 | } 518 | } 519 | 520 | // convert the string to a number value 521 | var num:Number = Number( input ); 522 | 523 | if ( isFinite( num ) && !isNaN( num ) ) 524 | { 525 | // the token for the number that we've read 526 | return JSONToken.create( JSONTokenType.NUMBER, num ); 527 | } 528 | else 529 | { 530 | parseError( "Number " + num + " is not valid!" ); 531 | } 532 | 533 | return null; 534 | } 535 | 536 | /** 537 | * Reads the next character in the input 538 | * string and advances the character location. 539 | * 540 | * @return The next character in the input string, or 541 | * null if we've read past the end. 542 | */ 543 | private final function nextChar():String 544 | { 545 | return ch = jsonString.charAt( loc++ ); 546 | } 547 | 548 | /** 549 | * Advances the character location past any 550 | * sort of white space and comments 551 | */ 552 | private final function skipIgnored():void 553 | { 554 | var originalLoc:int; 555 | 556 | // keep trying to skip whitespace and comments as long 557 | // as we keep advancing past the original location 558 | do 559 | { 560 | originalLoc = loc; 561 | skipWhite(); 562 | skipComments(); 563 | } while ( originalLoc != loc ); 564 | } 565 | 566 | /** 567 | * Skips comments in the input string, either 568 | * single-line or multi-line. Advances the character 569 | * to the first position after the end of the comment. 570 | */ 571 | private function skipComments():void 572 | { 573 | if ( ch == '/' ) 574 | { 575 | // Advance past the first / to find out what type of comment 576 | nextChar(); 577 | switch ( ch ) 578 | { 579 | case '/': // single-line comment, read through end of line 580 | 581 | // Loop over the characters until we find 582 | // a newline or until there's no more characters left 583 | do 584 | { 585 | nextChar(); 586 | } while ( ch != '\n' && ch != '' ) 587 | 588 | // move past the \n 589 | nextChar(); 590 | 591 | break; 592 | 593 | case '*': // multi-line comment, read until closing */ 594 | 595 | // move past the opening * 596 | nextChar(); 597 | 598 | // try to find a trailing */ 599 | while ( true ) 600 | { 601 | if ( ch == '*' ) 602 | { 603 | // check to see if we have a closing / 604 | nextChar(); 605 | if ( ch == '/' ) 606 | { 607 | // move past the end of the closing */ 608 | nextChar(); 609 | break; 610 | } 611 | } 612 | else 613 | { 614 | // move along, looking if the next character is a * 615 | nextChar(); 616 | } 617 | 618 | // when we're here we've read past the end of 619 | // the string without finding a closing */, so error 620 | if ( ch == '' ) 621 | { 622 | parseError( "Multi-line comment not closed" ); 623 | } 624 | } 625 | 626 | break; 627 | 628 | // Can't match a comment after a /, so it's a parsing error 629 | default: 630 | parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); 631 | } 632 | } 633 | 634 | } 635 | 636 | 637 | /** 638 | * Skip any whitespace in the input string and advances 639 | * the character to the first character after any possible 640 | * whitespace. 641 | */ 642 | private final function skipWhite():void 643 | { 644 | // As long as there are spaces in the input 645 | // stream, advance the current location pointer 646 | // past them 647 | while ( isWhiteSpace( ch ) ) 648 | { 649 | nextChar(); 650 | } 651 | 652 | } 653 | 654 | /** 655 | * Determines if a character is whitespace or not. 656 | * 657 | * @return True if the character passed in is a whitespace 658 | * character 659 | */ 660 | private final function isWhiteSpace( ch:String ):Boolean 661 | { 662 | // Check for the whitespace defined in the spec 663 | if ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ) 664 | { 665 | return true; 666 | } 667 | // If we're not in strict mode, we also accept non-breaking space 668 | else if ( !strict && ch.charCodeAt( 0 ) == 160 ) 669 | { 670 | return true; 671 | } 672 | 673 | return false; 674 | } 675 | 676 | /** 677 | * Determines if a character is a digit [0-9]. 678 | * 679 | * @return True if the character passed in is a digit 680 | */ 681 | private final function isDigit( ch:String ):Boolean 682 | { 683 | return ( ch >= '0' && ch <= '9' ); 684 | } 685 | 686 | /** 687 | * Determines if a character is a hex digit [0-9A-Fa-f]. 688 | * 689 | * @return True if the character passed in is a hex digit 690 | */ 691 | private final function isHexDigit( ch:String ):Boolean 692 | { 693 | return ( isDigit( ch ) || ( ch >= 'A' && ch <= 'F' ) || ( ch >= 'a' && ch <= 'f' ) ); 694 | } 695 | 696 | /** 697 | * Raises a parsing error with a specified message, tacking 698 | * on the error location and the original string. 699 | * 700 | * @param message The message indicating why the error occurred 701 | */ 702 | public final function parseError( message:String ):void 703 | { 704 | throw new JSONParseError( message, loc, jsonString ); 705 | } 706 | } 707 | 708 | } 709 | --------------------------------------------------------------------------------