日志更新

Simple JavaScript Template : substitute

我们在平常使用字符串拼接的时候(如下例),会发现代码的可维护性和易读性将变得更加糟糕(代码中一堆的变量、双引号、单引号, 加号等,相信当情况更为复杂时,头一定发晕):

var url= 'http://www.plannabc.net/',
    title= '落草为根——专注前端技术&关注用户体验',
    text = '怿飞's Blog';

var link = '<a href="' + url + '" title="' + title+ '">' + text+ '</a>';

如果上述代码变为:

var obj = {
    url: "http://www.plannabc.net/",
    title: "落草为根——专注前端技术&关注用户体验",
    text: "怿飞's Blog"
};
var link = '<a href="{url}" title="{title}">{text}</a>';
substitute(link, obj)

一切变得怡然自得。

substitute 函数的实现思路其实很简单:使用 String 的 replace 函数,在 replace 函数中用正则匹配除模板中的要替换的标签(“{key}”),并进行替换:

function substitute (str, obj) {
    if (!(Object.prototype.toString.call(str) === '[object String]')) {
        return '';
    }

    // {}, new Object(), new Class()
    // Object.prototype.toString.call(node=document.getElementById("xx")) : ie678 == '[object Object]', other =='[object HTMLElement]'
    // 'isPrototypeOf' in node : ie678 === false , other === true
    if(!(Object.prototype.toString.call(obj) === '[object Object]' && 'isPrototypeOf' in obj)) {
        return str;
    }

    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/replace
    return str.replace(/\{([^{}]+)\}/g, function(match, key) {
        var value = obj[key];
        return ( value !== undefined) ? ''+value :'';
    });
}

substitute 函数将模板中的标签 {key} 替换成 obj 中对应的 value(obj[key] or obj[key].toString()),如果不存在,则替换成空字符。

如果模板中某些内容不需要替换的怎么办?比如:{some text need brace}

  1. 可以增加新的语法,人为控制不需要替换的模板标签,比如:\{key\}
  2. 使用更为少见的字符作为模板标签,避免与常规情况撞车,比如:{{key}}

当然如果输入的数据 obj 为不完全信任的数据(比如:XSS)时,可以增加字符的转义:

function escape(s) {
    s = String(s === null ? "" : s);
    return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
        switch(s) {
            case "&": return "&amp;";
            case "\\": return "\\\\";
            case '"': return '&quot;';
            case "'": return '&#39;';
            case "<": return "&lt;";
            case ">": return "&gt;";
            default: return s;
        }
    });
}

我们再看看 YUI3 中的实现 :https://github.com/yui/yui3/blob/master/src/substitute/js/substitute.js

YUI3中做了更多的处理 substitute(s, o, f, recurse)

  1. 允许传入 fn,fn 将对 obj 中对应的 key/value 进行处理,返回新的 value。
  2. 如果支持 Y.dump 函数,将把 value 是对象的转成一定格式的字符串,如果不支持,直接返回对象的 toString。
  3. value 值为非对象、非字符串和非数字时 ,保持原标签不作替换。
  4. 如果 recurse 参数设置为 true,将进行标签的递归替换。

其实一般的时候简单的方式就够用了。面对自己编写库或组件的时候,都会有如下的选择:

  1. 大而全,涵盖所有的扩展,能捕捉到所有的异常。
  2. 小而精,满足一般的需求,特殊情况可定制,异常可通过约定控制。

选择最合适的才是最重要的,对于自己,通常偏向后者。

用python将文本转成图片

#-*- coding:utf-8 -*-

import Image, ImageDraw, ImageFont, uuid

def text2png(text):
    # config:
    adTexts = ['---------------', 'http://www.planabc.net']
    imgBg = '#FFFFFF'
    textColor = "#000000"
    adColor = "#FF0000"
    ttf = "C:\Windows\Fonts\STXIHEI.TTF"
    fontSize = 20
    tmp = 'tmp/'

    # Build rich text for ads
    ads = []
    for adText in adTexts:
        ads += [(adText.decode('utf-8'), adColor)]

    # Format wrapped lines to rich text
    bodyTexts = [""]
    l = 0
    # x.decode() ==> unicode
    for character in text.decode('utf-8'):
        c = character
        delta = len(c)
        if c == '\n':
            bodyTexts += [""]
            l = 0
        elif l + delta > 40:
            bodyTexts += [c]
            l = delta
        else:
            bodyTexts[-1] += c
            l += delta
            
    body = [(text, textColor) for text in bodyTexts]
    body += ads

    # Draw picture
    img = Image.new("RGB", (330, len(body) * fontSize + 5), imgBg)
    # Ref: http://blog.163.com/zhao_yunsong/blog/static/34059309200762781023987/
    draw = ImageDraw.Draw(img)
    font = ImageFont.truetype(ttf, fontSize)
    for num, (text, color) in enumerate(body):
        draw.text((2, fontSize * num), text, font=font, fill=color)

    # Write result to a temp file
    filename = uuid.uuid4().hex + ".png"
    file = open(tmp + filename, "wb")
    img.save(file, "PNG")

    return tmp + filename

if __name__ == '__main__':
    text2png( "怿飞")

参考自:http://simple-is-better.com/news/detail-289

domReady Function

上次写过 《模拟兼容性的 addDOMLoadEvent 事件》,昨天抽时间整理成了 domReady 函数

使用非常方便:

domReady(function () {
    // Dom is loaded! You can do anything!
});

测试案例:http://blank.github.com/domready/test/

代码如下(注释较为详尽,就不再说明了):

/**
 *  domready.js - Specify a function to execute when the DOM is fully loaded.
 *  Copyright (c) 2011 Blank Zheng (blankzheng@gmail.com)
 *  http://www.planabc.net
 */

(function (doc, win) {
    var isReady = 0,
        isBind = 0,
        fns = [],
        testEl = doc.createElement('p'),
        bindReady,
        init;

        win.domReady = function(fn){
            bindReady(fn);

            if (isReady) {
                fn();
            } else {
                fns.push(fn);
            }
        };

        bindReady = function (){
            if(isBind) return;
            isBind = 1;

            // Catch cases where domReady is called after the browser event has already occurred.
            // readyState: "uninitalized"、"loading"、"interactive"、"complete" 、"loaded"
            if(doc.readyState === "complete") {
                init();
            } else if (doc.addEventListener) {
                doc.addEventListener("DOMContentLoaded", function () {
                    doc.removeEventListener("DOMContentLoaded", arguments.callee, false);
                    init();
                }, false);
                win.addEventListener("onload", init, false);
            } else if(doc.attachEvent) {
                // In IE, ensure firing before onload, maybe late but safe also for iframes.
                doc.attachEvent("onreadystatechange", function() {
                    if (doc.readyState === "complete") {
                        doc.detachEvent("onreadystatechange", arguments.callee);
                        init();
                    }
                });
                win.attachEvent("onload", init);

                // If IE and not a frame, continually check to see if the document is ready.
                if(testEl.doScroll && win == win.top){
                    doScrollCheck();
                }
            }
        };

        // Process items when the DOM is ready.
        init = function () {
            isReady = 1;

            // Make sure body exists, at least, in case IE gets a little overzealous.
            // This is taked directly from jQuery's implementation.
            if (!doc.body) {
                setTimeout(init, 10);
                return;
            }

            for (var i = 0, l = fns.length; i < l; i++) {
                fns[i]();
            }
            fns = [];
        };

        function doScrollCheck() {
            if(isReady) return;

            try {
                // If IE is used, use the trick by Diego Perini
                // http://javascript.nwbox.com/IEContentLoaded/
                testEl.doScroll('left');
            } catch (e) {
                setTimeout(doScrollCheck, 10);
                return;
            }
            
            init();
        }

})(document, window);

/**
 * Ref:
 * http://www.planabc.net/2009/07/30/adddomloadevent/
 * https://github.com/ded/domready/blob/master/ready.js
 * https://github.com/Cu7l4ss/DomReady-script/blob/master/DomReady.js
 * https://github.com/jakobmattsson/onDomReady/blob/master/ondomready.js
 **/

如果平时做一些简单测试,可以使用下面 MINI 的 domReady 函数:

function domReady(fn) {
    // "uninitalized"、"loading"、"interactive"、"complete" 、"loaded"
    /in/.test(document.readyState) ? window.setTimeout(function() { domReady(fn); }, 10) : fn();
}

URI 中的一些规范

In RFC 3986

//URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] 

gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="

reserved = gen-delims / sub-delims
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
percent-encoded = "%" HEXDIG HEXDIG

pchar = unreserved / percent-encoded / sub-delims / ":" / "@"

query = *( pchar / "/" / "?" ) // "?" query 
fragment = *( pchar / "/" / "?" ) //"#" fragment