├── .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 |
28 |
29 |
30 |
欢迎您:[nick], 好评率:[seller_credit.good_num]
31 |

32 |
33 |
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