日志更新

哥俩好:oninput & onpropertychange

传统对于文本框(input)的输入可通过键盘的 onkeydown / onkeypress / onkeyup 来监测,但在处理较多细节时存在诟病比如: cut(剪切) / paste(复制) / undo(撤销) / redo(重做) / drag & drop(拖拽)/ 输入法等。

而 oninput & onpropertychange 事件基本可以解决上面的诟病:

oninput 事件作为 HTML5 中的标准事件,基本除了IE6 / IE7 / IE8 外的最新浏览器均支持(注:1、原先的 Opera 的虽支持,但依然存在部分传统的诟病,从 Opera 11+ 开始,已修复,更加完美;2、IE9 也支持)。

function(input, callback){
    if ("onpropertychange" in input) { //IE6/IE7/IE8
        input.onpropertychange = function(){
            if (window.event.propertyName == "value"){
                callback.call(this, window.event)
            }
        }
    } else {
        // Fix Firefox Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=195696
        input.addEventListener("input", callback, false);
    }
}

提示:

  1. oninput 事件:当 JS 改变 value 值或从浏览器的自动下拉提示中选值时,不会触发。
  2. onpropertychange 事件:当 input 设置为不可用(disable=true)时,不会触发。

参考文章:

将字符实体引用转换成 Unicode 字符

首先我们一起来看下 Character entities references (HTML Entities)和 Numeric Character Reference (NCR)的异同:

HTML Entities 的格式如:&lt;,NCR 的格式如:&#60;&#x3c;,均都表示“<” 字符。

HTML 中规定了 Character entity references,在 “24.2.1 The list of characters” 列出了 HTML Entities 和 NCR 的对应关系,例如:

<!ENTITY nbsp   CDATA "&#160;" -- no-break space = non-breaking space, U+00A0 ISOnum -->
<!ENTITY iexcl  CDATA "&#161;" -- inverted exclamation mark, U+00A1 ISOnum -->
<!ENTITY yen    CDATA "&#165;" -- yen sign = yuan sign, U+00A5 ISOnum --> 

那在 Python 中我们如何将 HTML Entities 和 NCR 转换成普通字符呢?

在回答这个问题之前,我们做一些简单的回顾:

group 方法

group([group1,…]) 

group 属于 Match Object 对象拥有的方法,返回匹配到的一个或者多个子组。如果是一个参数,那么结果返回字符串,如果是多个参数,则返回元组。group1 的默认值为 0 (将返回所有的匹配值),如果 groupX 的值是 [1…99] 范围之内的,那么将匹配对应括号组的字符串。如果组号是负的或者比 pattern 中定义的组号大,那么将抛出 IndexError 异常。若 pattern 没有匹配到,但 group 匹配到,那么 group 的值也为 None。如果一个 pattern 可以匹配多个,那么组对应匹配的最后一个。

re.sub 方法

re.sub(pattern , replace , string [, count])

sub 属于 re 模块的字符串替换和修改函数,其在目标字符串中查找与正则相匹配的字符串,并将其替换成指定的字符串。

  • pattern 参数——需要匹配的正则规则
  • replace 参数——指定用来替换的字符串或函数。如果 replace 是函数,则会对所有的匹配都回调此函数,这个函数使用单个 Match Object 作为参数,然后返回替换后的字符串。
  • string 参数——目标字符串
  • count 参数——最多替换的次数,未指定,则将替换所有匹配到的字符串

re.sub() 的使用案例如下:

import re
def dashrepl(matchobj):
    if matchobj.group(0) == '-':
        return ' '
    else:
        return '-'
re.sub('-{1,2}', dashrepl, 'pro----gram-files')

# result: 'pro--gram files'

htmlentitydefs

htmlentitydefs 有三个属性,详细如下:

  • entitydefs:A dictionary mapping XHTML 1.0 entity definitions to their replacement text in ISO Latin-1.
  • name2codepoint:A dictionary that maps HTML entity names to the Unicode codepoints. New in version 2.3.
  • codepoint2name:A dictionary that maps Unicode codepoints to HTML entity names.

实际存在的形式大致如下:

entitydefs = {'AElig': '\xc6', 'Aacute': '\xc1', 'Acirc': '\xc2', ...}
name2codepoint = {'AElig': 198, 'Aacute': 193, 'Acirc': 194, ...}
codepoint2name = {34: 'quot', 38: 'amp', 60: 'lt', 62: 'gt', ...}

对于我们来说,此时最有用的是 name2codepoint 属性,比如:“<”,name 是 lt,我们可以通过 name2codepoint[lt] 获得其 code point:60。

unichr 方法

unichr 是字符串的方法(unichr(int)),可以将整数转化成相应的 Unicode 字符,比如: unichr(60) –> u'\u003c' or u'<'

import re, htmlentitydefs

##
# Removes HTML or XML character references and entities from a text string.
#
# @param text The HTML (or XML) source text.
# @return The plain text, as a Unicode string, if necessary.

def unescape(text):
    def convert(matchobj):
        text = matchobj.group(0)
        if text[:2] == "&#":
            # Numeric Character Reference
            try:
                if text[:3] == "&#x":
                    return unichr(int(text[3:-1], 16))
                else:
                    return unichr(int(text[2:-1]))
            except ValueError:
                pass
        else:
            # Character entities references
            try:
                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
            except KeyError:
                pass
        return text # Return Unicode characters
    return re.sub("&#?\w+;", convert, text)

扩展阅读:

使用 JScript 创建 .exe 或 .dll 文件

什么是 JScript?

JScript 是由微软开发的活动脚本语言,基于 ECMAScript 规范实现。Internet Explorer 中的 JavaScript,实际上是指 JScript。JScript 已被 Windows Script Host(WSH)支持(WSH 中的 JavaScript shell scripting:C:\> cscript jslint.js)。JScript 最新的版本(JScript.NET)基于 ECMAScript 4.0 ,并且可以在 .Net 环境下编译。

.NET Framwork 中包含有 JScript 编译器 :JScriptCompiler (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\jsc.exe),其可以将 JScript 文件编译为一个 .exe 或者 .dll 文件。

为了方便使用,我们可以将 JScriptCompiler 的路径加入到环境变量(环境变量 –> 系统变量 –> Path)中。在 CMD 程序运行窗口中直接调用命令 “jsc”,就可以看到编译器相关的帮助选项。

jsc [选项] <源文件> [[选项] <源文件>…]

JScript 编译器选项

– 输出文件 -
/out:<file> 指定二进制输出文件的名称
/t[arget]:exe 创建控制台应用程序(默认)
/t[arget]:winexe 创建 Windows 应用程序
/t[arget]:library 创建库程序集
/platform:<platform> 限制此代码可以在其上运行的平台;必须是 x86、Itanium、x64 或 anycpu。默认为 anycpu

– 输入文件 -
/autoref[+|-] 基于导入的命名空间和完全限定名称自动引用程序集(默认情
况下为 on)
/lib:<path> 指定要在其中搜索引用的附加目录
/r[eference]:<file list> 从指定的程序集文件引用元数据 <file list>: <assembly name>[;<assembly name>...]

– 资源 -
/win32res:<file> 指定 Win32 资源文件(.res)
/res[ource]:<info> 嵌入指定的资源 <info>: <filename>[,<name>[,public|private]]
/linkres[ource]:<info> 将指定的资源链接到此程序集 <info>: <filename>[,<name>[,public|private]]

– 代码生成 -
/debug[+|-] 发出调试信息
/fast[+|-] 禁用语言功能以使代码更好地生成
/warnaserror[+|-] 将警告视为错误
/w[arn]:<level> 设置警告等级(0-4)

– 杂项 -
@<filename> 有关更多选项,请阅读响应文件
/? 显示帮助
/help 显示帮助
/d[efine]:<symbols> 定义条件编译符号
/nologo 不显示编译器版权标志
/print[+|-] 提供 print() 函数

– 高级 -
/codepage:<id> 使用指定的代码页 ID 打开源文件
/lcid:<id> 将指定的 LCID 用于消息和默认代码页
/nostdlib[+|-] 不导入标准库(mscorlib.dll)并将 autoref 默认值更改为 off
/utf8output[+|-] 以 UTF-8 字符编码形式发出编译器输出
/versionsafe[+|-] 为没有标记为“override”或“hide”的成员指定默认值

创建 .exe 文件

先创建 JS 文件(C:\test\helloWorld.js),内容如下:

var date = new Date();
print('Hello World! \nToday is ' + date );

接着我们进行编译:

C:\test>jsc helloWorld.js

Microsoft (R) JScript Compiler version 8.00.50727
for Microsoft (R) .NET Framework version 2.0.50727
Copyright (C) Microsoft Corporation 1996-2005。保留所有权利。

你会惊讶的发现,C:\test 目录下多了个 helloWorld.exe 文件,非常简单吧,呵呵

最后我们可以直接执行 helloWorld.exe 文件:

C:\test>helloWorld

Hello World!
Today is Fri Jun 3 23:13:20 UTC+8 2011

大功告成!!

创建 .dll 文件

.dll 文件的创建也同样非常简单:

package LibHW {
    class HelloWorld {
        function run() {
            var date = new Date();
            return 'Hello World! \nToday is ' + date;
        }
    }
}

编译语句:

C:\test>jsc /t:library LibHW.js

对于生成的 LibHW.dll 文件,我们可以通过创建新的 .exe 文件(consumer.js –> consumer.exe)以导入模块的方式调用(类似于 Python)。

先创建 consumer.js 文件:

import LibHW;
var hw = new LibHW.HelloWorld();
print(hw.run());

然后编译 consumer.js 文件,执行 consumer.exe:

C:\test>jsc consumer.js

Microsoft (R) JScript Compiler version 8.00.50727
for Microsoft (R) .NET Framework version 2.0.50727
Copyright (C) Microsoft Corporation 1996-2005。保留所有权利。

C:\test>consumer

Hello World!
Today is Sat Jun 4 00:42:35 UTC+8 2011

当然你还可以创建 windows 的应用,上例中的 consumer.js 文件修改如下:

import System.Windows.Forms; // this has a MessageBox class
import LibHW;

var hw = new LibHW.HelloWorld();
MessageBox.Show(
    hw.run(),
    "Dude!",
    MessageBoxButtons.OK,
    MessageBoxIcon.Exclamation
);

编译语句:

C:\test>jsc /t:winexe consumer.js

双击新生成的 consumer.exe 文件,哈哈,是不是很有成就感!

从上面的例子可以看到 JScript 的潜力无限,没有做不到,只有想不到。

扩展阅读:

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. 小而精,满足一般的需求,特殊情况可定制,异常可通过约定控制。

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