├── .gitignore ├── public ├── images │ └── logintb.png ├── stylesheets │ └── style.css └── js │ ├── json2.js │ └── handlebars.js ├── test ├── TopAPItest.js ├── signtest.js ├── httpposttest.js └── Unittest.js ├── views ├── index.hbs ├── APITestTool.hbs ├── index_jssdk.hbs └── main.hbs ├── routes ├── apitest.js ├── index.js ├── success.js └── main.js ├── package.json ├── SDK └── index.js ├── util ├── sign.js ├── TopHelper.js └── TopAPI.js ├── Gruntfile.js ├── appconfig.js ├── app.js ├── README └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea -------------------------------------------------------------------------------- /public/images/logintb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz121star/TOP-API-Nodejs-seed/HEAD/public/images/logintb.png -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /test/TopAPItest.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"), 2 | TopAPI = require("../util/TopAPI"); 3 | 4 | 5 | describe('TopAPItest', function () { 6 | describe('Post', function () { 7 | it.skip('#extend() ', function () { 8 | var a = TopAPI.PostAPI(); 9 | a.should.have.ownProperty('name'); 10 | a.should.have.ownProperty('age'); 11 | }) 12 | }) 13 | }) -------------------------------------------------------------------------------- /test/signtest.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | 3 | SignByHmacMd5=require("../util/sign").SignByHmacMd5; 4 | describe('signtest', function(){ 5 | describe('Sign', function(){ 6 | it('SignByHmacMd5 ', function(){ 7 | var SignInfo= SignByHmacMd5("12hello","Zshd12HMhaziUNXI"); 8 | assert.equal("D8B26D5970D69B9CE0E8426404119C39", SignInfo); 9 | }) 10 | }) 11 | }) -------------------------------------------------------------------------------- /views/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 淘拍档 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /routes/apitest.js: -------------------------------------------------------------------------------- 1 | var TopHelper = require("../util/TopHelper"), 2 | _ = require("underscore"), 3 | TopAPI = require("../util/TopAPI"); 4 | 5 | config = require('./../appconfig').Config; 6 | exports.index = function (req, res) { 7 | var clientsession = req.cookies["client_session"]; 8 | TopAPI.Execute = _.partial(TopAPI.PostAPI, config.RestUrl(), config.AppKey, config.AppSecret, clientsession); 9 | res.render('APITestTool'); 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "3.2.2", 10 | "hbs": "*", 11 | "underscore": "1.4.4", 12 | "moment": "2.0.0", 13 | "async":"0.2.8" 14 | }, 15 | "devDependencies": { 16 | "http-post": "*", 17 | "should": "*", 18 | "chai": "*", 19 | "mocha": "*", 20 | "grunt": "~0.4.1", 21 | "grunt-mocha-cli": "~1.0.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * GET home page. 3 | */ 4 | var config = require("../appconfig").Config, 5 | appkey = config.AppKey, 6 | appsecret = config.AppSecret, 7 | 8 | SignTaobao = require("../util/sign").SignTaobao; 9 | exports.index = function (req, res) { 10 | 11 | /*var SignInfo= SignTaobao(appkey,appsecret); 12 | res.cookie("timestamp",SignInfo.timestamp); 13 | res.cookie("sign",SignInfo.sign); 14 | 15 | res.render('index', { title: 'Express','AppKey':config.AppKey });*/ 16 | 17 | 18 | res.render('index', { 19 | app_loginurl:config.ContainerUrl(), 20 | AppKey:config.AppKey 21 | }) 22 | 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /test/httpposttest.js: -------------------------------------------------------------------------------- 1 | /* 2 | var http = require('http'); 3 | http.post = require('http-post'); 4 | var assert = require("assert"); 5 | describe('httpposttest', function () { 6 | var html = "23"; 7 | beforeEach(function (done) { 8 | http.post('http://localhost:3002/rest', { name:'Sam', email:'sam@emberlabs.org' }, function (res) { 9 | res.setEncoding('utf8'); 10 | res.on('data', function (chunk) { 11 | html = chunk; 12 | done(); 13 | }); 14 | 15 | } ) 16 | 17 | }) 18 | it('#post() ', function (done) { 19 | 20 | assert.equal("HelloSam",html); 21 | done(); 22 | }); 23 | 24 | })*/ 25 | -------------------------------------------------------------------------------- /routes/success.js: -------------------------------------------------------------------------------- 1 | TopHelper = require("../util/TopHelper"); 2 | var config = require('./../appconfig').Config; 3 | exports.index = function (req, res) { 4 | var qstring = req.query; 5 | if (qstring.top_appkey) { 6 | if (TopHelper.VerifyTopResponse(qstring.top_parameters, qstring.top_session, qstring.top_sign, config.AppKey, config.AppSecret)){ 7 | var nick=TopHelper.GetParameters(qstring.top_parameters,"visitor_nick"); 8 | res.cookie(nick,qstring.top_session); 9 | res.cookie("client_session",qstring.top_session); 10 | res.send("验证成功"); 11 | res.redirect("/main"); 12 | 13 | } 14 | else{ 15 | res.send("验证回调地址参数失败"); 16 | } 17 | } 18 | else { 19 | res.send("登录验证失败"); 20 | } 21 | 22 | 23 | }; -------------------------------------------------------------------------------- /SDK/index.js: -------------------------------------------------------------------------------- 1 | var TopHelper = require("../util/TopHelper"), 2 | _ = require("underscore"), 3 | TopAPI = require("../util/TopAPI"); 4 | exports.index = function (req, res) { 5 | var method=req.body.method||{}; 6 | var options=req.body.options||{}; 7 | TopAPI.Execute(method, options, function (data) { 8 | res.set({ 9 | 'Content-Type': 'application/json' 10 | }); 11 | res.send(data); 12 | }); 13 | /* TopAPI.Execute("taobao.items.onsale.get", 14 | {fields: "approve_status,num_iid,title,nick,type,cid,pic_url,num,props,valid_thru, list_time,price,has_discount,has_invoice,has_warranty,has_showcase, modified,delist_time,postage_id,seller_cids,outer_id" 15 | }, function (data) { 16 | res.contentType="application/json"; 17 | res.send(data); 18 | });*/ 19 | } 20 | -------------------------------------------------------------------------------- /util/sign.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var config = require('./../appconfig').Config; 3 | 4 | /** 5 | * Sign taobao api for JSSDK`. 6 | * 7 | * @param {appkey} Key 8 | * @param {appsecret} Secret 9 | * @api public 10 | */ 11 | 12 | var Sign = function (appkey,appsecret) { 13 | var timestamp = new Date().getTime(); 14 | var message = appsecret + "app_key" + appkey + "timestamp" + timestamp + appsecret; 15 | var sign = SignByHmacMd5(message, appsecret); 16 | return{ 17 | timestamp:timestamp, 18 | sign:sign 19 | } 20 | } 21 | var SignByHmacMd5 = function (message, secret) { 22 | 23 | var hmac = crypto.createHmac("md5", secret); 24 | var result= hmac.update(message).digest('hex'); 25 | result=result.toUpperCase(); 26 | return result; 27 | } 28 | 29 | exports.SignTaobao=Sign; 30 | exports.SignByHmacMd5=SignByHmacMd5; -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | // 配置 3 | grunt.initConfig({ 4 | mochacli: { 5 | options: { 6 | require: ['should'], 7 | files: 'test/*.js' 8 | }, 9 | spec: { 10 | options: { 11 | reporter: 'spec', 12 | timeout:10000 13 | 14 | } 15 | }, 16 | nyan: { 17 | options: { 18 | reporter: 'nyan' 19 | } 20 | }, 21 | doc: { 22 | options: { 23 | reporter: 'doc' 24 | } 25 | } 26 | } 27 | }); 28 | grunt.loadNpmTasks('grunt-mocha-cli'); 29 | grunt.registerTask('test', ['mochacli:spec']); 30 | grunt.registerTask('default', ['mochacli:doc']); 31 | }; -------------------------------------------------------------------------------- /appconfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @type {Object} 4 | */ 5 | exports.Config={ 6 | /** 7 | * IsSandBox 1=沙箱开启 0=非沙箱(正式环境) 8 | */ 9 | SanBox:1, 10 | /** 11 | * AppKey 12 | */ 13 | AppKey:"1021487406", 14 | /** 15 | * AppSecret 16 | */ 17 | AppSecret:"sandbox4c9c72a69655707068f1f30c9", 18 | 19 | ContainerUrl:function(){ 20 | if(this.SanBox===0) 21 | return "http://container.open.taobao.com/container?appkey="+this.AppKey; 22 | else 23 | return "http://container.api.tbsandbox.com/container?appkey="+this.AppKey ; 24 | }, 25 | /** 26 | * 请求环境URL ,默认为沙箱环境地址 27 | * @return {*} 28 | * @constructor 29 | */ 30 | RestUrl:function(){ 31 | if(this.SanBox===0) 32 | return "http://gw.api.taobao.com/router/rest"; 33 | else 34 | return "http://gw.api.tbsandbox.com/router/rest"; 35 | } 36 | 37 | 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /util/TopHelper.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | _ = require("underscore"), 3 | moment = require('moment'), 4 | URL = require('url'), 5 | crypto= require("crypto"), 6 | querystring = require('querystring'); 7 | 8 | /** 9 | * 验证回调地址的签名是否合法 10 | * @param TOP私有参数(未经Base64解密后的) 11 | * @param 私有会话码 12 | * @param TOP回调签名(经过URL反编码的) 13 | * @param 应用公钥 14 | * @param 应用密钥 15 | * @constructor 16 | */ 17 | var VerifyTopResponse = function (topParams, topSession, topSign, appKey, appSecret) { 18 | var result; 19 | result=appKey+topParams+topSession+appSecret; 20 | 21 | var _tempbytes = new Buffer(result, 'utf8') 22 | var result = crypto.createHash("md5") 23 | .update(_tempbytes) 24 | .digest("base64"); 25 | if(topSign===result) return true; 26 | else return false; 27 | } 28 | /** 29 | * 解析回调地址中top_parameters中的值 30 | * @param parameters 31 | * @param key 32 | * @return {*} 33 | * @constructor 34 | */ 35 | var GetParameters=function(parameters,key){ 36 | var Data=new Buffer(parameters,'base64').toString(); 37 | return querystring.parse(Data)[key]; 38 | 39 | } 40 | exports.VerifyTopResponse=VerifyTopResponse; 41 | exports.GetParameters=GetParameters; -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var express = require('express') 6 | , routes = require('./routes') 7 | , http = require('http') 8 | ,SDK=require('./SDK/index') 9 | , path = require('path'); 10 | routes.successRoute=require('./routes/success'); 11 | routes.main=require('./routes/main'); 12 | routes.apitest=require('./routes/apitest'); 13 | 14 | var app = express(); 15 | 16 | 17 | // all environments 18 | 19 | app.engine('hbs', require('hbs').__express); 20 | app.set('port', process.env.PORT || 3002); 21 | app.set('views', __dirname + '/views'); 22 | app.set('view engine', 'hbs'); 23 | app.use(express.favicon()); 24 | app.use(express.logger('dev')); 25 | app.use(express.bodyParser()); 26 | app.use(express.methodOverride()); 27 | app.use(express.cookieParser('your secret here')); 28 | app.use(express.session()); 29 | app.use(app.router); 30 | app.use(express.static(path.join(__dirname, 'public'))); 31 | 32 | // development only 33 | if ('development' == app.get('env')) { 34 | app.use(express.errorHandler()); 35 | } 36 | 37 | app.get('/', routes.index); 38 | app.get('/success',routes.successRoute.index); 39 | app.get('/main',routes.main.index); 40 | app.get('/apitest',routes.apitest.index) 41 | app.post('/rest', SDK.index); 42 | 43 | 44 | http.createServer(app).listen(app.get('port'), function () { 45 | console.log('Express server listening on port ' + app.get('port')); 46 | }); 47 | -------------------------------------------------------------------------------- /views/APITestTool.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | API测试工具 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 |
19 | 例如:taobao.user.seller.get
20 | 数据格式必须为{"key":"value"},属性和值必须都用双引号,否则会调用失败
21 |
22 | 23 |
24 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /routes/main.js: -------------------------------------------------------------------------------- 1 | var TopHelper = require("../util/TopHelper"), 2 | _ = require("underscore"), 3 | TopAPI = require("../util/TopAPI"); 4 | 5 | config = require('./../appconfig').Config; 6 | exports.index = function (req, res) { 7 | 8 | var clientsession = req.cookies["client_session"]; 9 | TopAPI.Execute = _.partial(TopAPI.PostAPI, config.RestUrl(), config.AppKey, config.AppSecret, clientsession); 10 | res.render('main' ); 11 | /* TopAPI.Execute("taobao.shop.remainshowcase.get", {}, function (data) { 12 | res.render('main', { 13 | result: data 14 | }) 15 | });*/ 16 | /* TopAPI.Execute("taobao.items.onsale.get", 17 | {fields: "approve_status,num_iid,title,nick,type,cid,pic_url,num,props,valid_thru, list_time,price,has_discount,has_invoice,has_warranty,has_showcase, modified,delist_time,postage_id,seller_cids,outer_id" 18 | }, function (data) { 19 | res.render('main', { 20 | result: JSON.parse(data), 21 | title: "商铺信息" 22 | }) 23 | });*/ 24 | /* var result; 25 | async.parallel([ 26 | 27 | function (cb) { 28 | TopAPI.Execute("taobao.shop.remainshowcase.get", {}, cb) 29 | }, 30 | function (cb) { 31 | TopAPI.Execute("taobao.items.onsale.get", 32 | {fields: "approve_status,num_iid,title,nick,type,cid,pic_url,num,props,valid_thru, list_time,price,has_discount,has_invoice,has_warranty,has_showcase, modified,delist_time,postage_id,seller_cids,outer_id" 33 | }, cb) 34 | } 35 | 36 | ], function (err, result) { 37 | console.log("err:"+err); 38 | console.log("result:"+result); 39 | res.render('main', { 40 | result: err 41 | }) 42 | });*/ 43 | 44 | 45 | }; -------------------------------------------------------------------------------- /views/index_jssdk.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 淘拍档 5 | 6 | 7 | 8 | 9 | Hello:{{title}} 10 |
11 | 头像: 12 | 昵称: 13 | 性别: 14 | ID: 15 | 等级 16 |
17 |
18 | 19 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/Unittest.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"), 2 | moment = require('moment'), 3 | TopAPI = require("../util/TopAPI"); 4 | 5 | 6 | describe('Unittest', function () { 7 | describe('unit', function () { 8 | var hash = { 9 | app_key:"appkey", 10 | method:"method", 11 | session:"session", 12 | format:"json", 13 | v:"2.0", 14 | sign_method:"md5", 15 | sign:"z" 16 | }; 17 | 18 | it('#HashSort() ', function () { 19 | var _temp = hash; 20 | var a = TopAPI.HashSort(_temp); 21 | 22 | }) 23 | it.skip('#CreateSign()', function () { 24 | assert.equal("miaozhuangapp_keyappkeyformatjsonmethodmethodsessionsessionsignzsign_methodmd5v2.0miaozhuang", TopAPI.CreateSign(TopAPI.HashSort(hash), "miaozhuang")); 25 | }) 26 | 27 | 28 | it('#md5()', function () { 29 | /* var a= new Buffer("miaozhuang", 'utf8') 30 | 31 | var result= require("crypto").createHash("md5") 32 | .update(a) 33 | .digest("hex").toUpperCase();; 34 | assert.equal("4FB2E84E54F84B2E9341BAC53A83157D",result);*/ 35 | 36 | 37 | /* var post_data = require('querystring').stringify({name:'miaozhuang',sex:'mansdasdsads',age:'18'}); 38 | 39 | assert.equal(39, post_data.length);*/ 40 | /* var url= require('url'); 41 | var u= url.parse("http://www.webxml.com.cn/WebServices/WeatherWebService.asmx/getSupportCity");*/ 42 | 43 | var a= moment(new Date()).format("YYYY-MM-DD HH:mm:ss").toString() 44 | assert.equal("a",a); 45 | 46 | }) 47 | 48 | it('#sHTTPPost()', function (done) { 49 | var d; 50 | //TopAPI.HTTPPost("gw.api.tbsandbox.com","/router/rest",{ format:"json"}, function (data) { 51 | TopAPI.HTTPPost("www.webxml.com.cn","/WebServices/WeatherWebService.asmx/getSupportCity",{byProvinceName:'辽宁'}, function (data) { 52 | d = data; 53 | 54 | 55 | assert.equal("M", d); 56 | done(data); 57 | }) 58 | 59 | 60 | }) 61 | 62 | }) 63 | }) -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | [淘宝开放平台](http://open.taobao.com/index.htm "淘宝开放平台TOP")API调用Nodejs版本实现 2 | 3 | 淘宝开放平台(Taobao Open Platform以下简称TOP)API调用方式可以通过[此处](http://open.taobao.com/doc/detail.htm?spm=0.0.0.0.XtOdmR&id=111)查看。 4 | 5 | 本代码为调用TOP API的Nodejs实现版本。可以在此基础上进行开发. 6 | 7 | **你可以通过如下的步骤,轻松进入开发状态** 8 | 9 | 10 | 1,获得代码 11 | 12 | > `https://github.com/mz121star/taobao.git` 13 | 14 | 2,进入项目目录,执行如下命令安装项目的依赖包 15 | > `npm install` 16 | 17 | 3,修改appconfig.js文件中的Appkey和Appsecret的值(该值从你的应用证书页面获得) 18 | 19 | > `AppKey:"从淘宝获得的Appkey",` 20 | > 21 | > `AppSecret:"从淘宝获得的AppSecret"` 22 | 23 | 24 | **到此为止**,你已经完成了项目运行起来所需要的全部步骤,你可以通过如下命令启动web服务. 25 | 26 | > `node app.js` 27 | 28 | 此时,你可以通过浏览器访问***http://localhost:3000***查看效果,本项目实现了一个简单的获取卖家当前出售商品的demo。至此,所有演示已经结束,你可以通过如下的介绍,开始进行项目的继续开发。 29 | 30 | ## **开始开发** ## 31 | 32 | 开发前首先用你喜欢的编辑器打开项目,此处我推荐使用[webstorm](http://www.jetbrains.com/webstorm/)。 33 | 34 | 项目结构 35 | 36 | |-public `/*用来存放网站的静态资源,包括css js images等` 37 | 38 | |-routes `/*存放controller文件` 39 | |-SDK 40 | 41 | |-index.js /*提供了前端通过js调用API的功能 通过$.ajax("/rest")访问到的及时此文件 42 | 43 | |-test `/*存放测试文件,项目测试采用Mocha` 44 | 45 | |-util 调用淘宝API的核心功能 46 | 47 | |- sign.js 用于签名JSSDK 48 | |- TopAPI.js api调用的核心文件 49 | |- TopHelper.js一些工具类,如加密等 50 | |-views 存放前端HTML文件,项目使用**handlebars**模板引擎,所以文件后缀为**hbs** 51 | 52 | 53 | **API签名流程** 54 | 55 | 程序登陆页为index。 56 | 57 | 登录成功后会回调到/success处理(此处为你在开发中心配置的回调地址)。在此处理中需要做如下判断。 58 | 将成功验证的客户端session分配到cookie中,然后将页面跳转向**/main**(具体功能)页面. 59 | 60 | 61 | > if (TopHelper.VerifyTopResponse(qstring.top_parameters, qstring.top_session, qstring.top_sign, config.AppKey, config.AppSecret)){ 62 | > 63 | > var nick=TopHelper.GetParameters(qstring.top_parameters,"visitor_nick"); 64 | > res.cookie(nick,qstring.top_session); 65 | > res.cookie("client_session",qstring.top_session); 66 | > res.send("验证成功"); 67 | > res.redirect("/main"); 68 | > 69 | > } 70 | 71 | 72 | 73 | 74 | 75 | ## 如何在nodejs中调用API? ## 76 | 77 | 你只需通过如下一行代码即可调用([具体API](http://open.taobao.com/doc/category_list.htm?spm=0.0.0.0.t3GHlD&id=102)) 78 | 79 | > TopAPI.Execute(method, options, function (data) {}) 80 | > /*method 需要调用API的名称,如:“taobao.item.get” 81 | > /*API需要传入的参数 82 | 83 | ## 如何利用jquery调用API? ## 84 | 和上面大体相同,你可以通过如下方式。 85 | > $.post("/rest", {method:"taobao.items.onsale.get", options:options }, function (data) {}) 86 | 87 | ##API测试工具 88 | 登录后访问/apitest,可以进入API测试工具 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## [淘宝开放平台](http://open.taobao.com/index.htm "淘宝开放平台TOP")API调用Nodejs版本实现 ## 3 | 4 | 淘宝开放平台(Taobao Open Platform以下简称TOP)API调用方式可以通过[此处](http://open.taobao.com/doc/detail.htm?spm=0.0.0.0.XtOdmR&id=111)查看。 5 | 6 | 本代码为调用TOP API的Nodejs实现版本。可以在此基础上进行开发. 7 | 8 | **你可以通过如下的步骤,轻松进入开发状态** 9 | 10 | 11 | 1,获得代码 12 | 13 | > `git clone https://github.com/mz121star/taobao.git` 14 | 15 | 2,进入项目目录,执行如下命令安装项目的依赖包 16 | > `npm install` 17 | 18 | 3,修改appconfig.js文件中的Appkey和Appsecret的值(该值从你的应用证书页面获得) 19 | 20 | > `AppKey:"从淘宝获得的Appkey",` 21 | > 22 | > `AppSecret:"从淘宝获得的AppSecret"` 23 | 24 | 25 | **到此为止**,你已经完成了项目运行起来所需要的全部步骤,你可以通过如下命令启动web服务. 26 | 27 | > `node app.js` 28 | 29 | 此时,你可以通过浏览器访问***http://localhost:3000***查看效果,本项目实现了一个简单的获取卖家当前出售商品的demo。至此,所有演示已经结束,你可以通过如下的介绍,开始进行项目的继续开发。 30 | 31 | ## **开始开发** ## 32 | 33 | 开发前首先用你喜欢的编辑器打开项目,此处我推荐使用[webstorm](http://www.jetbrains.com/webstorm/)。 34 | 35 | 项目结构 36 | 37 | |-public `/*用来存放网站的静态资源,包括css js images等` 38 | 39 | |-routes `/*存放controller文件` 40 | |-SDK 41 | 42 | |-index.js /*提供了前端通过js调用API的功能 通过$.ajax("/rest")访问到的及时此文件 43 | 44 | |-test `/*存放测试文件,项目测试采用Mocha` 45 | 46 | |-util 调用淘宝API的核心功能 47 | 48 | |- sign.js 用于签名JSSDK 49 | |- TopAPI.js api调用的核心文件 50 | |- TopHelper.js一些工具类,如加密等 51 | |-views 存放前端HTML文件,项目使用**handlebars**模板引擎,所以文件后缀为**hbs** 52 | 53 | 54 | **API签名流程** 55 | 56 | 程序登陆页为index。 57 | 58 | 登录成功后会回调到/success处理(此处为你在开发中心配置的回调地址)。在此处理中需要做如下判断。 59 | 将成功验证的客户端session分配到cookie中,然后将页面跳转向**/main**(具体功能)页面. 60 | 61 | 62 | > if (TopHelper.VerifyTopResponse(qstring.top_parameters, qstring.top_session, qstring.top_sign, config.AppKey, config.AppSecret)){ 63 | > 64 | > var nick=TopHelper.GetParameters(qstring.top_parameters,"visitor_nick"); 65 | > res.cookie(nick,qstring.top_session); 66 | > res.cookie("client_session",qstring.top_session); 67 | > res.send("验证成功"); 68 | > res.redirect("/main"); 69 | > 70 | > } 71 | 72 | 73 | 74 | 75 | 76 | ## 如何在nodejs中调用API? ## 77 | 78 | 你只需通过如下一行代码即可调用([具体API](http://open.taobao.com/doc/category_list.htm?spm=0.0.0.0.t3GHlD&id=102)) 79 | 80 | > TopAPI.Execute(method, options, function (data) {}) 81 | > /*method 需要调用API的名称,如:“taobao.item.get” 82 | > /*API需要传入的参数 83 | 84 | ## 如何利用jquery调用API? ## 85 | 86 | 和上面大体相同,你可以通过如下方式 87 | 88 | > $.post("/rest", {method:"taobao.items.onsale.get", options:options }, function (data) {}) 89 | 90 | ##API测试工具 91 | 登录后访问/apitest,可以进入API测试工具 92 | -------------------------------------------------------------------------------- /util/TopAPI.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | _ = require("underscore"), 3 | moment = require('moment'), 4 | URL = require('url'), 5 | crypto = require('crypto'), 6 | querystring = require('querystring'); 7 | 8 | 9 | var Execute; 10 | /** 11 | * 12 | * @param url Container Url 13 | * @param appkey 14 | * @param appSecret 15 | * @param session client session 16 | * @param method request api 17 | * @param params 18 | * @constructor 19 | */ 20 | var PostAPI = function (url, appkey, appSecret, session, method, params, callback) { 21 | _.extend(params, { 22 | app_key:appkey, 23 | method:method, 24 | session:session, 25 | timestamp:moment(new Date()).format("YYYY-MM-DD HH:mm:ss").toString(), 26 | format:"json", 27 | v:"2.0", 28 | sign_method:"md5" 29 | }); 30 | _.extend(params, {sign:CreateSign(params, appSecret)}); 31 | var u = URL.parse(url); 32 | HTTPPost(u.hostname, u.pathname, u.port, params, callback); 33 | } 34 | /** 35 | * HTTP Post请求 36 | * @param host 主机名 37 | * @param path 请求路径 38 | * @param port 端口 39 | * @param data 请求的body参数 40 | * @param callback 回调函数,接受返回的数据 41 | * @constructor 42 | */ 43 | var HTTPPost = function (host, path, port, data, callback) { 44 | var result = ""; 45 | var post_data = querystring.stringify(data); 46 | console.log(post_data.length); 47 | var options = { 48 | hostname:host, 49 | port:port || 80, 50 | path:path || "/", 51 | method:'POST', 52 | headers:{ 53 | 'Content-Type':"application/x-www-form-urlencoded;charset=utf-8", 54 | 'Content-Length':post_data.length, 55 | 'User-Agent':"SpaceTimeApp2.0", 56 | 'Keep-Alive':true, 57 | 'timeout':300000 58 | } 59 | }; 60 | 61 | 62 | var req = http.request(options, function (res) { 63 | // console.log('STATUS: ' + res.statusCode); 64 | // console.log('HEADERS: ' + JSON.stringify(res.headers)); 65 | res.setEncoding('utf8'); 66 | res.on('data', function (chunk) { 67 | result+=chunk; 68 | }); 69 | res.on('end', function (chunk) { 70 | 71 | console.log("over:"+result); 72 | callback(result); 73 | 74 | }); 75 | }); 76 | 77 | req.on('error', function (e) { 78 | console.log('problem with request: ' + e.message); 79 | }); 80 | 81 | // write data to request body 82 | req.write(post_data + '\n'); 83 | req.end(); 84 | 85 | }; 86 | /** 87 | * 给TOP请求签名 API v2.0 88 | * @param params 89 | * @param secret 90 | * @return {String} 91 | * @constructor 92 | */ 93 | var CreateSign = function (params, secret) { 94 | // 第一步:把字典按Key的字母顺序排序 95 | params = HashSort(params); 96 | // 第二步:把所有参数名和参数值串在一起 97 | var query = secret; 98 | _.each(params, function (item, index) { 99 | query += index + item; 100 | }) 101 | query += secret; 102 | // 第三步:使用MD5加密,把二进制转化为大写的十六进制 103 | var _tempbytes = new Buffer(query, 'utf8') 104 | var result = require("crypto").createHash("md5") 105 | .update(_tempbytes) 106 | .digest("hex").toUpperCase(); 107 | return result; 108 | } 109 | 110 | /** 111 | * 对hash的key字母进行排序 112 | * @param params 113 | * @return {Object} 114 | * @constructor 115 | */ 116 | var HashSort = function (params) { 117 | return _.object(_.pairs(params).sort()); 118 | } 119 | 120 | exports.PostAPI = PostAPI; 121 | exports.HashSort = HashSort; 122 | exports.CreateSign = CreateSign; 123 | exports.HTTPPost = HTTPPost; 124 | exports.Execute=Execute; -------------------------------------------------------------------------------- /views/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 25 | 26 | 27 |
api测试工具
28 |
29 |
30 | 欢迎您:[nick], 好评率:[seller_credit.good_num] 31 | 头像 32 |
33 |
34 |
35 | 36 | [title] 37 | 38 |
39 |
40 |
41 | 42 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /public/js/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | json2.js 3 | 2012-10-08 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, regexp: true */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (typeof JSON !== 'object') { 163 | JSON = {}; 164 | } 165 | 166 | (function () { 167 | 'use strict'; 168 | 169 | function f(n) { 170 | // Format integers to have at least two digits. 171 | return n < 10 ? '0' + n : n; 172 | } 173 | 174 | if (typeof Date.prototype.toJSON !== 'function') { 175 | 176 | Date.prototype.toJSON = function (key) { 177 | 178 | return isFinite(this.valueOf()) 179 | ? this.getUTCFullYear() + '-' + 180 | f(this.getUTCMonth() + 1) + '-' + 181 | f(this.getUTCDate()) + 'T' + 182 | f(this.getUTCHours()) + ':' + 183 | f(this.getUTCMinutes()) + ':' + 184 | f(this.getUTCSeconds()) + 'Z' 185 | : null; 186 | }; 187 | 188 | String.prototype.toJSON = 189 | Number.prototype.toJSON = 190 | Boolean.prototype.toJSON = function (key) { 191 | return this.valueOf(); 192 | }; 193 | } 194 | 195 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 196 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | gap, 198 | indent, 199 | meta = { // table of character substitutions 200 | '\b': '\\b', 201 | '\t': '\\t', 202 | '\n': '\\n', 203 | '\f': '\\f', 204 | '\r': '\\r', 205 | '"' : '\\"', 206 | '\\': '\\\\' 207 | }, 208 | rep; 209 | 210 | 211 | function quote(string) { 212 | 213 | // If the string contains no control characters, no quote characters, and no 214 | // backslash characters, then we can safely slap some quotes around it. 215 | // Otherwise we must also replace the offending characters with safe escape 216 | // sequences. 217 | 218 | escapable.lastIndex = 0; 219 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 220 | var c = meta[a]; 221 | return typeof c === 'string' 222 | ? c 223 | : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 224 | }) + '"' : '"' + string + '"'; 225 | } 226 | 227 | 228 | function str(key, holder) { 229 | 230 | // Produce a string from holder[key]. 231 | 232 | var i, // The loop counter. 233 | k, // The member key. 234 | v, // The member value. 235 | length, 236 | mind = gap, 237 | partial, 238 | value = holder[key]; 239 | 240 | // If the value has a toJSON method, call it to obtain a replacement value. 241 | 242 | if (value && typeof value === 'object' && 243 | typeof value.toJSON === 'function') { 244 | value = value.toJSON(key); 245 | } 246 | 247 | // If we were called with a replacer function, then call the replacer to 248 | // obtain a replacement value. 249 | 250 | if (typeof rep === 'function') { 251 | value = rep.call(holder, key, value); 252 | } 253 | 254 | // What happens next depends on the value's type. 255 | 256 | switch (typeof value) { 257 | case 'string': 258 | return quote(value); 259 | 260 | case 'number': 261 | 262 | // JSON numbers must be finite. Encode non-finite numbers as null. 263 | 264 | return isFinite(value) ? String(value) : 'null'; 265 | 266 | case 'boolean': 267 | case 'null': 268 | 269 | // If the value is a boolean or null, convert it to a string. Note: 270 | // typeof null does not produce 'null'. The case is included here in 271 | // the remote chance that this gets fixed someday. 272 | 273 | return String(value); 274 | 275 | // If the type is 'object', we might be dealing with an object or an array or 276 | // null. 277 | 278 | case 'object': 279 | 280 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 281 | // so watch out for that case. 282 | 283 | if (!value) { 284 | return 'null'; 285 | } 286 | 287 | // Make an array to hold the partial results of stringifying this object value. 288 | 289 | gap += indent; 290 | partial = []; 291 | 292 | // Is the value an array? 293 | 294 | if (Object.prototype.toString.apply(value) === '[object Array]') { 295 | 296 | // The value is an array. Stringify every element. Use null as a placeholder 297 | // for non-JSON values. 298 | 299 | length = value.length; 300 | for (i = 0; i < length; i += 1) { 301 | partial[i] = str(i, value) || 'null'; 302 | } 303 | 304 | // Join all of the elements together, separated with commas, and wrap them in 305 | // brackets. 306 | 307 | v = partial.length === 0 308 | ? '[]' 309 | : gap 310 | ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' 311 | : '[' + partial.join(',') + ']'; 312 | gap = mind; 313 | return v; 314 | } 315 | 316 | // If the replacer is an array, use it to select the members to be stringified. 317 | 318 | if (rep && typeof rep === 'object') { 319 | length = rep.length; 320 | for (i = 0; i < length; i += 1) { 321 | if (typeof rep[i] === 'string') { 322 | k = rep[i]; 323 | v = str(k, value); 324 | if (v) { 325 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 326 | } 327 | } 328 | } 329 | } else { 330 | 331 | // Otherwise, iterate through all of the keys in the object. 332 | 333 | for (k in value) { 334 | if (Object.prototype.hasOwnProperty.call(value, k)) { 335 | v = str(k, value); 336 | if (v) { 337 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 338 | } 339 | } 340 | } 341 | } 342 | 343 | // Join all of the member texts together, separated with commas, 344 | // and wrap them in braces. 345 | 346 | v = partial.length === 0 347 | ? '{}' 348 | : gap 349 | ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' 350 | : '{' + partial.join(',') + '}'; 351 | gap = mind; 352 | return v; 353 | } 354 | } 355 | 356 | // If the JSON object does not yet have a stringify method, give it one. 357 | 358 | if (typeof JSON.stringify !== 'function') { 359 | JSON.stringify = function (value, replacer, space) { 360 | 361 | // The stringify method takes a value and an optional replacer, and an optional 362 | // space parameter, and returns a JSON text. The replacer can be a function 363 | // that can replace values, or an array of strings that will select the keys. 364 | // A default replacer method can be provided. Use of the space parameter can 365 | // produce text that is more easily readable. 366 | 367 | var i; 368 | gap = ''; 369 | indent = ''; 370 | 371 | // If the space parameter is a number, make an indent string containing that 372 | // many spaces. 373 | 374 | if (typeof space === 'number') { 375 | for (i = 0; i < space; i += 1) { 376 | indent += ' '; 377 | } 378 | 379 | // If the space parameter is a string, it will be used as the indent string. 380 | 381 | } else if (typeof space === 'string') { 382 | indent = space; 383 | } 384 | 385 | // If there is a replacer, it must be a function or an array. 386 | // Otherwise, throw an error. 387 | 388 | rep = replacer; 389 | if (replacer && typeof replacer !== 'function' && 390 | (typeof replacer !== 'object' || 391 | typeof replacer.length !== 'number')) { 392 | throw new Error('JSON.stringify'); 393 | } 394 | 395 | // Make a fake root object containing our value under the key of ''. 396 | // Return the result of stringifying the value. 397 | 398 | return str('', {'': value}); 399 | }; 400 | } 401 | 402 | 403 | // If the JSON object does not yet have a parse method, give it one. 404 | 405 | if (typeof JSON.parse !== 'function') { 406 | JSON.parse = function (text, reviver) { 407 | 408 | // The parse method takes a text and an optional reviver function, and returns 409 | // a JavaScript value if the text is a valid JSON text. 410 | 411 | var j; 412 | 413 | function walk(holder, key) { 414 | 415 | // The walk method is used to recursively walk the resulting structure so 416 | // that modifications can be made. 417 | 418 | var k, v, value = holder[key]; 419 | if (value && typeof value === 'object') { 420 | for (k in value) { 421 | if (Object.prototype.hasOwnProperty.call(value, k)) { 422 | v = walk(value, k); 423 | if (v !== undefined) { 424 | value[k] = v; 425 | } else { 426 | delete value[k]; 427 | } 428 | } 429 | } 430 | } 431 | return reviver.call(holder, key, value); 432 | } 433 | 434 | 435 | // Parsing happens in four stages. In the first stage, we replace certain 436 | // Unicode characters with escape sequences. JavaScript handles many characters 437 | // incorrectly, either silently deleting them, or treating them as line endings. 438 | 439 | text = String(text); 440 | cx.lastIndex = 0; 441 | if (cx.test(text)) { 442 | text = text.replace(cx, function (a) { 443 | return '\\u' + 444 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 445 | }); 446 | } 447 | 448 | // In the second stage, we run the text against regular expressions that look 449 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 450 | // because they can cause invocation, and '=' because it can cause mutation. 451 | // But just to be safe, we want to reject all unexpected forms. 452 | 453 | // We split the second stage into 4 regexp operations in order to work around 454 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 455 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 456 | // replace all simple value tokens with ']' characters. Third, we delete all 457 | // open brackets that follow a colon or comma or that begin the text. Finally, 458 | // we look to see that the remaining characters are only whitespace or ']' or 459 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 460 | 461 | if (/^[\],:{}\s]*$/ 462 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 463 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 464 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 465 | 466 | // In the third stage we use the eval function to compile the text into a 467 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 468 | // in JavaScript: it can begin a block or an object literal. We wrap the text 469 | // in parens to eliminate the ambiguity. 470 | 471 | j = eval('(' + text + ')'); 472 | 473 | // In the optional fourth stage, we recursively walk the new structure, passing 474 | // each name/value pair to a reviver function for possible transformation. 475 | 476 | return typeof reviver === 'function' 477 | ? walk({'': j}, '') 478 | : j; 479 | } 480 | 481 | // If the text is not JSON parseable, then a SyntaxError is thrown. 482 | 483 | throw new SyntaxError('JSON.parse'); 484 | }; 485 | } 486 | }()); -------------------------------------------------------------------------------- /public/js/handlebars.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2011 by Yehuda Katz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | // lib/handlebars/base.js 26 | 27 | /*jshint eqnull:true*/ 28 | this.Handlebars = {}; 29 | 30 | (function(Handlebars) { 31 | 32 | Handlebars.VERSION = "1.0.0-rc.3"; 33 | Handlebars.COMPILER_REVISION = 2; 34 | 35 | Handlebars.REVISION_CHANGES = { 36 | 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 37 | 2: '>= 1.0.0-rc.3' 38 | }; 39 | 40 | Handlebars.helpers = {}; 41 | Handlebars.partials = {}; 42 | 43 | Handlebars.registerHelper = function(name, fn, inverse) { 44 | if(inverse) { fn.not = inverse; } 45 | this.helpers[name] = fn; 46 | }; 47 | 48 | Handlebars.registerPartial = function(name, str) { 49 | this.partials[name] = str; 50 | }; 51 | 52 | Handlebars.registerHelper('helperMissing', function(arg) { 53 | if(arguments.length === 2) { 54 | return undefined; 55 | } else { 56 | throw new Error("Could not find property '" + arg + "'"); 57 | } 58 | }); 59 | 60 | var toString = Object.prototype.toString, functionType = "[object Function]"; 61 | 62 | Handlebars.registerHelper('blockHelperMissing', function(context, options) { 63 | var inverse = options.inverse || function() {}, fn = options.fn; 64 | 65 | 66 | var ret = ""; 67 | var type = toString.call(context); 68 | 69 | if(type === functionType) { context = context.call(this); } 70 | 71 | if(context === true) { 72 | return fn(this); 73 | } else if(context === false || context == null) { 74 | return inverse(this); 75 | } else if(type === "[object Array]") { 76 | if(context.length > 0) { 77 | return Handlebars.helpers.each(context, options); 78 | } else { 79 | return inverse(this); 80 | } 81 | } else { 82 | return fn(context); 83 | } 84 | }); 85 | 86 | Handlebars.K = function() {}; 87 | 88 | Handlebars.createFrame = Object.create || function(object) { 89 | Handlebars.K.prototype = object; 90 | var obj = new Handlebars.K(); 91 | Handlebars.K.prototype = null; 92 | return obj; 93 | }; 94 | 95 | Handlebars.logger = { 96 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, 97 | 98 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, 99 | 100 | // can be overridden in the host environment 101 | log: function(level, obj) { 102 | if (Handlebars.logger.level <= level) { 103 | var method = Handlebars.logger.methodMap[level]; 104 | if (typeof console !== 'undefined' && console[method]) { 105 | console[method].call(console, obj); 106 | } 107 | } 108 | } 109 | }; 110 | 111 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; 112 | 113 | Handlebars.registerHelper('each', function(context, options) { 114 | var fn = options.fn, inverse = options.inverse; 115 | var i = 0, ret = "", data; 116 | 117 | if (options.data) { 118 | data = Handlebars.createFrame(options.data); 119 | } 120 | 121 | if(context && typeof context === 'object') { 122 | if(context instanceof Array){ 123 | for(var j = context.length; i 2) { 331 | expected.push("'" + this.terminals_[p] + "'"); 332 | } 333 | if (this.lexer.showPosition) { 334 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; 335 | } else { 336 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); 337 | } 338 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); 339 | } 340 | } 341 | if (action[0] instanceof Array && action.length > 1) { 342 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); 343 | } 344 | switch (action[0]) { 345 | case 1: 346 | stack.push(symbol); 347 | vstack.push(this.lexer.yytext); 348 | lstack.push(this.lexer.yylloc); 349 | stack.push(action[1]); 350 | symbol = null; 351 | if (!preErrorSymbol) { 352 | yyleng = this.lexer.yyleng; 353 | yytext = this.lexer.yytext; 354 | yylineno = this.lexer.yylineno; 355 | yyloc = this.lexer.yylloc; 356 | if (recovering > 0) 357 | recovering--; 358 | } else { 359 | symbol = preErrorSymbol; 360 | preErrorSymbol = null; 361 | } 362 | break; 363 | case 2: 364 | len = this.productions_[action[1]][1]; 365 | yyval.$ = vstack[vstack.length - len]; 366 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; 367 | if (ranges) { 368 | yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; 369 | } 370 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); 371 | if (typeof r !== "undefined") { 372 | return r; 373 | } 374 | if (len) { 375 | stack = stack.slice(0, -1 * len * 2); 376 | vstack = vstack.slice(0, -1 * len); 377 | lstack = lstack.slice(0, -1 * len); 378 | } 379 | stack.push(this.productions_[action[1]][0]); 380 | vstack.push(yyval.$); 381 | lstack.push(yyval._$); 382 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; 383 | stack.push(newState); 384 | break; 385 | case 3: 386 | return true; 387 | } 388 | } 389 | return true; 390 | } 391 | }; 392 | /* Jison generated lexer */ 393 | var lexer = (function(){ 394 | var lexer = ({EOF:1, 395 | parseError:function parseError(str, hash) { 396 | if (this.yy.parser) { 397 | this.yy.parser.parseError(str, hash); 398 | } else { 399 | throw new Error(str); 400 | } 401 | }, 402 | setInput:function (input) { 403 | this._input = input; 404 | this._more = this._less = this.done = false; 405 | this.yylineno = this.yyleng = 0; 406 | this.yytext = this.matched = this.match = ''; 407 | this.conditionStack = ['INITIAL']; 408 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; 409 | if (this.options.ranges) this.yylloc.range = [0,0]; 410 | this.offset = 0; 411 | return this; 412 | }, 413 | input:function () { 414 | var ch = this._input[0]; 415 | this.yytext += ch; 416 | this.yyleng++; 417 | this.offset++; 418 | this.match += ch; 419 | this.matched += ch; 420 | var lines = ch.match(/(?:\r\n?|\n).*/g); 421 | if (lines) { 422 | this.yylineno++; 423 | this.yylloc.last_line++; 424 | } else { 425 | this.yylloc.last_column++; 426 | } 427 | if (this.options.ranges) this.yylloc.range[1]++; 428 | 429 | this._input = this._input.slice(1); 430 | return ch; 431 | }, 432 | unput:function (ch) { 433 | var len = ch.length; 434 | var lines = ch.split(/(?:\r\n?|\n)/g); 435 | 436 | this._input = ch + this._input; 437 | this.yytext = this.yytext.substr(0, this.yytext.length-len-1); 438 | //this.yyleng -= len; 439 | this.offset -= len; 440 | var oldLines = this.match.split(/(?:\r\n?|\n)/g); 441 | this.match = this.match.substr(0, this.match.length-1); 442 | this.matched = this.matched.substr(0, this.matched.length-1); 443 | 444 | if (lines.length-1) this.yylineno -= lines.length-1; 445 | var r = this.yylloc.range; 446 | 447 | this.yylloc = {first_line: this.yylloc.first_line, 448 | last_line: this.yylineno+1, 449 | first_column: this.yylloc.first_column, 450 | last_column: lines ? 451 | (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: 452 | this.yylloc.first_column - len 453 | }; 454 | 455 | if (this.options.ranges) { 456 | this.yylloc.range = [r[0], r[0] + this.yyleng - len]; 457 | } 458 | return this; 459 | }, 460 | more:function () { 461 | this._more = true; 462 | return this; 463 | }, 464 | less:function (n) { 465 | this.unput(this.match.slice(n)); 466 | }, 467 | pastInput:function () { 468 | var past = this.matched.substr(0, this.matched.length - this.match.length); 469 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); 470 | }, 471 | upcomingInput:function () { 472 | var next = this.match; 473 | if (next.length < 20) { 474 | next += this._input.substr(0, 20-next.length); 475 | } 476 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); 477 | }, 478 | showPosition:function () { 479 | var pre = this.pastInput(); 480 | var c = new Array(pre.length + 1).join("-"); 481 | return pre + this.upcomingInput() + "\n" + c+"^"; 482 | }, 483 | next:function () { 484 | if (this.done) { 485 | return this.EOF; 486 | } 487 | if (!this._input) this.done = true; 488 | 489 | var token, 490 | match, 491 | tempMatch, 492 | index, 493 | col, 494 | lines; 495 | if (!this._more) { 496 | this.yytext = ''; 497 | this.match = ''; 498 | } 499 | var rules = this._currentRules(); 500 | for (var i=0;i < rules.length; i++) { 501 | tempMatch = this._input.match(this.rules[rules[i]]); 502 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { 503 | match = tempMatch; 504 | index = i; 505 | if (!this.options.flex) break; 506 | } 507 | } 508 | if (match) { 509 | lines = match[0].match(/(?:\r\n?|\n).*/g); 510 | if (lines) this.yylineno += lines.length; 511 | this.yylloc = {first_line: this.yylloc.last_line, 512 | last_line: this.yylineno+1, 513 | first_column: this.yylloc.last_column, 514 | last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; 515 | this.yytext += match[0]; 516 | this.match += match[0]; 517 | this.matches = match; 518 | this.yyleng = this.yytext.length; 519 | if (this.options.ranges) { 520 | this.yylloc.range = [this.offset, this.offset += this.yyleng]; 521 | } 522 | this._more = false; 523 | this._input = this._input.slice(match[0].length); 524 | this.matched += match[0]; 525 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); 526 | if (this.done && this._input) this.done = false; 527 | if (token) return token; 528 | else return; 529 | } 530 | if (this._input === "") { 531 | return this.EOF; 532 | } else { 533 | return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 534 | {text: "", token: null, line: this.yylineno}); 535 | } 536 | }, 537 | lex:function lex() { 538 | var r = this.next(); 539 | if (typeof r !== 'undefined') { 540 | return r; 541 | } else { 542 | return this.lex(); 543 | } 544 | }, 545 | begin:function begin(condition) { 546 | this.conditionStack.push(condition); 547 | }, 548 | popState:function popState() { 549 | return this.conditionStack.pop(); 550 | }, 551 | _currentRules:function _currentRules() { 552 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; 553 | }, 554 | topState:function () { 555 | return this.conditionStack[this.conditionStack.length-2]; 556 | }, 557 | pushState:function begin(condition) { 558 | this.begin(condition); 559 | }}); 560 | lexer.options = {}; 561 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { 562 | 563 | var YYSTATE=YY_START 564 | switch($avoiding_name_collisions) { 565 | case 0: 566 | if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); 567 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); 568 | if(yy_.yytext) return 14; 569 | 570 | break; 571 | case 1: return 14; 572 | break; 573 | case 2: 574 | if(yy_.yytext.slice(-1) !== "\\") this.popState(); 575 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); 576 | return 14; 577 | 578 | break; 579 | case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; 580 | break; 581 | case 4: this.begin("par"); return 24; 582 | break; 583 | case 5: return 16; 584 | break; 585 | case 6: return 20; 586 | break; 587 | case 7: return 19; 588 | break; 589 | case 8: return 19; 590 | break; 591 | case 9: return 23; 592 | break; 593 | case 10: return 23; 594 | break; 595 | case 11: this.popState(); this.begin('com'); 596 | break; 597 | case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; 598 | break; 599 | case 13: return 22; 600 | break; 601 | case 14: return 36; 602 | break; 603 | case 15: return 35; 604 | break; 605 | case 16: return 35; 606 | break; 607 | case 17: return 39; 608 | break; 609 | case 18: /*ignore whitespace*/ 610 | break; 611 | case 19: this.popState(); return 18; 612 | break; 613 | case 20: this.popState(); return 18; 614 | break; 615 | case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30; 616 | break; 617 | case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30; 618 | break; 619 | case 23: yy_.yytext = yy_.yytext.substr(1); return 28; 620 | break; 621 | case 24: return 32; 622 | break; 623 | case 25: return 32; 624 | break; 625 | case 26: return 31; 626 | break; 627 | case 27: return 35; 628 | break; 629 | case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35; 630 | break; 631 | case 29: return 'INVALID'; 632 | break; 633 | case 30: /*ignore whitespace*/ 634 | break; 635 | case 31: this.popState(); return 37; 636 | break; 637 | case 32: return 5; 638 | break; 639 | } 640 | }; 641 | lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; 642 | lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; 643 | return lexer;})() 644 | parser.lexer = lexer; 645 | function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; 646 | return new Parser; 647 | })();; 648 | // lib/handlebars/compiler/base.js 649 | Handlebars.Parser = handlebars; 650 | 651 | Handlebars.parse = function(input) { 652 | 653 | // Just return if an already-compile AST was passed in. 654 | if(input.constructor === Handlebars.AST.ProgramNode) { return input; } 655 | 656 | Handlebars.Parser.yy = Handlebars.AST; 657 | return Handlebars.Parser.parse(input); 658 | }; 659 | 660 | Handlebars.print = function(ast) { 661 | return new Handlebars.PrintVisitor().accept(ast); 662 | };; 663 | // lib/handlebars/compiler/ast.js 664 | (function() { 665 | 666 | Handlebars.AST = {}; 667 | 668 | Handlebars.AST.ProgramNode = function(statements, inverse) { 669 | this.type = "program"; 670 | this.statements = statements; 671 | if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } 672 | }; 673 | 674 | Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { 675 | this.type = "mustache"; 676 | this.escaped = !unescaped; 677 | this.hash = hash; 678 | 679 | var id = this.id = rawParams[0]; 680 | var params = this.params = rawParams.slice(1); 681 | 682 | // a mustache is an eligible helper if: 683 | // * its id is simple (a single part, not `this` or `..`) 684 | var eligibleHelper = this.eligibleHelper = id.isSimple; 685 | 686 | // a mustache is definitely a helper if: 687 | // * it is an eligible helper, and 688 | // * it has at least one parameter or hash segment 689 | this.isHelper = eligibleHelper && (params.length || hash); 690 | 691 | // if a mustache is an eligible helper but not a definite 692 | // helper, it is ambiguous, and will be resolved in a later 693 | // pass or at runtime. 694 | }; 695 | 696 | Handlebars.AST.PartialNode = function(partialName, context) { 697 | this.type = "partial"; 698 | this.partialName = partialName; 699 | this.context = context; 700 | }; 701 | 702 | var verifyMatch = function(open, close) { 703 | if(open.original !== close.original) { 704 | throw new Handlebars.Exception(open.original + " doesn't match " + close.original); 705 | } 706 | }; 707 | 708 | Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { 709 | verifyMatch(mustache.id, close); 710 | this.type = "block"; 711 | this.mustache = mustache; 712 | this.program = program; 713 | this.inverse = inverse; 714 | 715 | if (this.inverse && !this.program) { 716 | this.isInverse = true; 717 | } 718 | }; 719 | 720 | Handlebars.AST.ContentNode = function(string) { 721 | this.type = "content"; 722 | this.string = string; 723 | }; 724 | 725 | Handlebars.AST.HashNode = function(pairs) { 726 | this.type = "hash"; 727 | this.pairs = pairs; 728 | }; 729 | 730 | Handlebars.AST.IdNode = function(parts) { 731 | this.type = "ID"; 732 | this.original = parts.join("."); 733 | 734 | var dig = [], depth = 0; 735 | 736 | for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } 741 | else if (part === "..") { depth++; } 742 | else { this.isScoped = true; } 743 | } 744 | else { dig.push(part); } 745 | } 746 | 747 | this.parts = dig; 748 | this.string = dig.join('.'); 749 | this.depth = depth; 750 | 751 | // an ID is simple if it only has one part, and that part is not 752 | // `..` or `this`. 753 | this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; 754 | 755 | this.stringModeValue = this.string; 756 | }; 757 | 758 | Handlebars.AST.PartialNameNode = function(name) { 759 | this.type = "PARTIAL_NAME"; 760 | this.name = name; 761 | }; 762 | 763 | Handlebars.AST.DataNode = function(id) { 764 | this.type = "DATA"; 765 | this.id = id; 766 | }; 767 | 768 | Handlebars.AST.StringNode = function(string) { 769 | this.type = "STRING"; 770 | this.string = string; 771 | this.stringModeValue = string; 772 | }; 773 | 774 | Handlebars.AST.IntegerNode = function(integer) { 775 | this.type = "INTEGER"; 776 | this.integer = integer; 777 | this.stringModeValue = Number(integer); 778 | }; 779 | 780 | Handlebars.AST.BooleanNode = function(bool) { 781 | this.type = "BOOLEAN"; 782 | this.bool = bool; 783 | this.stringModeValue = bool === "true"; 784 | }; 785 | 786 | Handlebars.AST.CommentNode = function(comment) { 787 | this.type = "comment"; 788 | this.comment = comment; 789 | }; 790 | 791 | })();; 792 | // lib/handlebars/utils.js 793 | 794 | var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; 795 | 796 | Handlebars.Exception = function(message) { 797 | var tmp = Error.prototype.constructor.apply(this, arguments); 798 | 799 | // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. 800 | for (var idx = 0; idx < errorProps.length; idx++) { 801 | this[errorProps[idx]] = tmp[errorProps[idx]]; 802 | } 803 | }; 804 | Handlebars.Exception.prototype = new Error(); 805 | 806 | // Build out our basic SafeString type 807 | Handlebars.SafeString = function(string) { 808 | this.string = string; 809 | }; 810 | Handlebars.SafeString.prototype.toString = function() { 811 | return this.string.toString(); 812 | }; 813 | 814 | (function() { 815 | var escape = { 816 | "&": "&", 817 | "<": "<", 818 | ">": ">", 819 | '"': """, 820 | "'": "'", 821 | "`": "`" 822 | }; 823 | 824 | var badChars = /[&<>"'`]/g; 825 | var possible = /[&<>"'`]/; 826 | 827 | var escapeChar = function(chr) { 828 | return escape[chr] || "&"; 829 | }; 830 | 831 | Handlebars.Utils = { 832 | escapeExpression: function(string) { 833 | // don't escape SafeStrings, since they're already safe 834 | if (string instanceof Handlebars.SafeString) { 835 | return string.toString(); 836 | } else if (string == null || string === false) { 837 | return ""; 838 | } 839 | 840 | if(!possible.test(string)) { return string; } 841 | return string.replace(badChars, escapeChar); 842 | }, 843 | 844 | isEmpty: function(value) { 845 | if (!value && value !== 0) { 846 | return true; 847 | } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { 848 | return true; 849 | } else { 850 | return false; 851 | } 852 | } 853 | }; 854 | })();; 855 | // lib/handlebars/compiler/compiler.js 856 | 857 | /*jshint eqnull:true*/ 858 | Handlebars.Compiler = function() {}; 859 | Handlebars.JavaScriptCompiler = function() {}; 860 | 861 | (function(Compiler, JavaScriptCompiler) { 862 | // the foundHelper register will disambiguate helper lookup from finding a 863 | // function in a context. This is necessary for mustache compatibility, which 864 | // requires that context functions in blocks are evaluated by blockHelperMissing, 865 | // and then proceed as if the resulting value was provided to blockHelperMissing. 866 | 867 | Compiler.prototype = { 868 | compiler: Compiler, 869 | 870 | disassemble: function() { 871 | var opcodes = this.opcodes, opcode, out = [], params, param; 872 | 873 | for (var i=0, l=opcodes.length; i 0) { 1361 | this.source[1] = this.source[1] + ", " + locals.join(", "); 1362 | } 1363 | 1364 | // Generate minimizer alias mappings 1365 | if (!this.isChild) { 1366 | for (var alias in this.context.aliases) { 1367 | this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; 1368 | } 1369 | } 1370 | 1371 | if (this.source[1]) { 1372 | this.source[1] = "var " + this.source[1].substring(2) + ";"; 1373 | } 1374 | 1375 | // Merge children 1376 | if (!this.isChild) { 1377 | this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; 1378 | } 1379 | 1380 | if (!this.environment.isSimple) { 1381 | this.source.push("return buffer;"); 1382 | } 1383 | 1384 | var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; 1385 | 1386 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } 1918 | return this.topStackName(); 1919 | }, 1920 | topStackName: function() { 1921 | return "stack" + this.stackSlot; 1922 | }, 1923 | flushInline: function() { 1924 | var inlineStack = this.inlineStack; 1925 | if (inlineStack.length) { 1926 | this.inlineStack = []; 1927 | for (var i = 0, len = inlineStack.length; i < len; i++) { 1928 | var entry = inlineStack[i]; 1929 | if (entry instanceof Literal) { 1930 | this.compileStack.push(entry); 1931 | } else { 1932 | this.pushStack(entry); 1933 | } 1934 | } 1935 | } 1936 | }, 1937 | isInline: function() { 1938 | return this.inlineStack.length; 1939 | }, 1940 | 1941 | popStack: function(wrapped) { 1942 | var inline = this.isInline(), 1943 | item = (inline ? this.inlineStack : this.compileStack).pop(); 1944 | 1945 | if (!wrapped && (item instanceof Literal)) { 1946 | return item.value; 1947 | } else { 1948 | if (!inline) { 1949 | this.stackSlot--; 1950 | } 1951 | return item; 1952 | } 1953 | }, 1954 | 1955 | topStack: function(wrapped) { 1956 | var stack = (this.isInline() ? this.inlineStack : this.compileStack), 1957 | item = stack[stack.length - 1]; 1958 | 1959 | if (!wrapped && (item instanceof Literal)) { 1960 | return item.value; 1961 | } else { 1962 | return item; 1963 | } 1964 | }, 1965 | 1966 | quotedString: function(str) { 1967 | return '"' + str 1968 | .replace(/\\/g, '\\\\') 1969 | .replace(/"/g, '\\"') 1970 | .replace(/\n/g, '\\n') 1971 | .replace(/\r/g, '\\r') + '"'; 1972 | }, 1973 | 1974 | setupHelper: function(paramSize, name, missingParams) { 1975 | var params = []; 1976 | this.setupParams(paramSize, params, missingParams); 1977 | var foundHelper = this.nameLookup('helpers', name, 'helper'); 1978 | 1979 | return { 1980 | params: params, 1981 | name: foundHelper, 1982 | callParams: ["depth0"].concat(params).join(", "), 1983 | helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") 1984 | }; 1985 | }, 1986 | 1987 | // the params and contexts arguments are passed in arrays 1988 | // to fill in 1989 | setupParams: function(paramSize, params, useRegister) { 1990 | var options = [], contexts = [], types = [], param, inverse, program; 1991 | 1992 | options.push("hash:" + this.popStack()); 1993 | 1994 | inverse = this.popStack(); 1995 | program = this.popStack(); 1996 | 1997 | // Avoid setting fn and inverse if neither are set. This allows 1998 | // helpers to do a check for `if (options.fn)` 1999 | if (program || inverse) { 2000 | if (!program) { 2001 | this.context.aliases.self = "this"; 2002 | program = "self.noop"; 2003 | } 2004 | 2005 | if (!inverse) { 2006 | this.context.aliases.self = "this"; 2007 | inverse = "self.noop"; 2008 | } 2009 | 2010 | options.push("inverse:" + inverse); 2011 | options.push("fn:" + program); 2012 | } 2013 | 2014 | for(var i=0; i