Google JavaScript Style Guide 中文简要翻译 – Style Rules部分

JavaScript Style Rules

2.1 Naming 命名
通常地,像下面一样命名:

functionNamesLikeThis
variableNamesLikeThis
ClassNamesLikeThis
EnumNamesLikeThis
methodNamesLikeThis
SYMBOLIC_CONSTANTS_LIKE_THIS

函数名、变量名、方法名:首字母小写
类名、枚举名:首字母大写
常量:恒大写,下划线分割

2.1.1 属性和方法
私有属性、变量和方法应该以下划线结尾
受保护属性、变量和方法不应当以下划线结尾(和公有一样)
关于私有、受保护,参考visibility一节

2.1.2 方法和函数的参数
可选的参数以「opt_」开头
如果函数接受不定参数,应当将此参数放在参数列表的最后,并且以var_args命名
但在函数体内不应使用var_args。使用arguments数组来代替。

2.1.3 属性的Getters和Setters
ECMAScript5的Getters和Setters不建议使用。
如果使用了,那么Getters中不应该修改可见的状态
错误的用法:

/**
 * WRONG -- Do NOT do this.
 */
var foo = { get next() { return this.nextId++; } };
};

2.1.4 存取函数
属性的Getters和Setters不是必需的。
但是,如果要写,那么Getters必须写成getFoo()、Setters必须写成setFoo(value)的形式。
布尔型的Getters可以写成isFoo(),并且通常看起来更自然。

2.1.5 命名空间
JavaScript没有原生的包或命名空间机制。
全局的命名冲突很难debug,而且在两个工程合并时会导致棘手的问题。
我们采取约定俗成的方式来防止冲突来实现代码重用。

2.1.5.1 为全局的代码使用命名空间
总是使用和工程名或类库名相同的词作为(伪)命名空间的前缀。
例如,如果你正在写一个叫”Sloth”的工程,合理的(伪)命名空间应该是sloth.* :

var sloth = {};

sloth.sleep = function() {
  ...
};

许多JavaScript类库提供创建命名空间的高级函数,如the Closure LibraryDojo toolkit

goog.provide('sloth');

sloth.sleep = function() {
  ...
};

2.1.5.2 明晰命名空间的所有权
当创建子命名空间时,确保父命名空间的所有人知道你在干什么。
例如,当你开始一个为sloths创建hats的工程时,确保Sloth团队知道你在使用sloth.hats命名空间

2.1.5.3 内部代码不能使用和外部代码相同的命名空间
外部代码是指不在你的代码库中的代码,它们独立地编译(通常是引入的第三方类库,但也有可能是别的团队写的类库)。
内部代码和外部代码的命名空间要严格分离。
比如,一个外部库定义了foo.hats的命名空间,foo.hats.*变得可用,
但你的代码不能在foo.hats下面定义任何符号。
错误的写法:

foo.require('foo.hats');

/**
 * WRONG -- Do NOT do this.
 * @constructor
 * @extend {foo.hats.RoundHat}
 */
foo.hats.BowlerHat = function() {
};

如果需要在外部命名空间中定义新的API时,应当显式的导出API函数(并且仅导出这些函数)
内部代码在调用这些函数时仍然使用内部名称,这可以保持一致并且编译器可以优化它们:

foo.provide('googleyhats.BowlerHat');

foo.require('foo.hats');

/**
 * @constructor
 * @extend {foo.hats.RoundHat}
 */
googleyhats.BowlerHat = function() {
  ...
};

goog.exportSymbol('foo.hats.BowlerHat', googleyhats.BowlerHat);

2.1.5.4 别名以增加可读性
使用本地别名替代全修饰名,以增加可读性。
本地别名必须和全修饰名的最后部分吻合。
例如:

/**
 * @constructor
 */
some.long.namespace.MyClass = function() {
};

/**
 * @param {some.long.namespace.MyClass} a
 */
some.long.namespace.MyClass.staticHelper = function(a) {
  ...
};

myapp.main = function() {
  var MyClass = some.long.namespace.MyClass;
  var staticHelper = some.long.namespace.MyClass.staticHelper;
  staticHelper(new MyClass());
};

不要别名命名空间。
错误的写法:

myapp.main = function() {
  var namespace = some.long.namespace;
  namespace.MyClass.staticHelper(new namespace.MyClass());
};

避免直接使用别名的属性,除非它是一个枚举
正确的写法:

/** @enum {string} */
some.long.namespace.Fruit = {
  APPLE: 'a',
  BANANA: 'b'
};

myapp.main = function() {
  var Fruit = some.long.namespace.Fruit;
  switch (fruit) {
    case Fruit.APPLE:
      ...
    case Fruit.BANANA:
      ...
  }
};

错误的写法:

myapp.main = function() {
  var MyClass = some.long.namespace.MyClass;
  MyClass.staticHelper(null);
};

永远不要在全局环境创建别名。请仅在函数内使用它们。

2.1.6 文件名
文件名保持全小写,以避免在大小写敏感的平台上发生混乱。
文件名的扩展名应该是js,除在分隔时使用「-」或「_」外,不应包括其他的标点符号。
(优选使用「-」)

2.2 自定义的toString()方法
必须总是能成功调用并且没有任何副作用。
自定义toString()是好的,但必须保证:
(1)总是能成功调用
(2)没有任何副作用
如果你的toString没有满足这些要求,很容易就会导致严重问题。
例如,如果toString()里调用了一个做断言的方法,断言失败时可能会尝试输出发生失败的类的名称(跟踪失败发生在哪里),而输出类名当然又要调用到toString()

2.3 延迟初始化
可以。
不总是可能在声明时就初始化变量,所以延迟初始化时可以的。

2.4 Explicit scope
总是使用显式范围
(待译)
Always use explicit scope – doing so increases portability and clarity. For example, don’t rely on window being in the scope chain. You might want to use your function in another application for which window is not the content window.

2.5 代码格式化
基本上使用C++的格式化规则
除了下面几点以外。

2.5.1 大括弧
为了避免解析器隐含的插入分号,总是在入口的那一行开始大括弧。
例如:

if (something) {
  // ...
} else {
  // ...
}

2.5.2 数组和对象的初始化
当一行能写得下时,可以全部写在一行:

var arr = [1, 2, 3];  // 「[」后面和「]」前面没有空格!
var obj = {a: 1, b: 2, c: 3};  // 「{」后面和「}」前面没有空格!

多行数组的初始化则缩进2个空格,和块的方式类似,例如:

// Object initializer.
var inset = {
  top: 10,
  right: 20,
  bottom: 15,
  left: 12
};

// Array initializer.
this.rows_ = [
  '"Slartibartfast" <fjordmaster@magrathea.com>',
  '"Zaphod Beeblebrox" <theprez@universe.gov>',
  '"Ford Prefect" <ford@theguide.com>',
  '"Arthur Dent" <has.no.tea@gmail.com>',
  '"Marvin the Paranoid Android" <marv@googlemail.com>',
  'the.mice@magrathea.com'
];

// Used in a method call.
goog.dom.createDom(goog.dom.TagName.DIV, {
  id: 'foo',
  className: 'some-css-class',
  style: 'display:none'
}, 'Hello, world!');

书写属性的初始化时不要对齐
正确的写法:

CORRECT_Object.prototype = {
  a: 0,
  b: 1,
  lengthyName: 2
};

错误的写法:

WRONG_Object.prototype = {
  a          : 0,
  b          : 1,
  lengthyName: 2
};

2.5.3 函数参数列表
如果可能,全部的参数都写在同一行。
但当一行超过80个字符位,为了更加容易阅读,必须换行。
为了节省空间,可以尽量写满80字符,也可以为了更加易读,每行只放一个参数。
每行缩进4个字符位,或者对齐圆括弧。

一些例子:

// Four-space, wrap at 80.  Works with very long function names, survives
// renaming without reindenting, low on space.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
    veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
  // ...
};

// Four-space, one argument per line.  Works with long function names,
// survives renaming, and emphasizes each argument.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
    veryDescriptiveArgumentNumberOne,
    veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy,
    artichokeDescriptorAdapterIterator) {
  // ...
};

// Parenthesis-aligned indentation, wrap at 80.  Visually groups arguments,
// low on space.
function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
             tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
  // ...
}

// Parenthesis-aligned, one argument per line.  Visually groups and
// emphasizes each individual argument.
function bar(veryDescriptiveArgumentNumberOne,
             veryDescriptiveArgumentTwo,
             tableModelEventHandlerProxy,
             artichokeDescriptorAdapterIterator) {
  // ...
}

当调用的函数本身已经缩进时,参数列可以选择再缩进4个字符也可以选择对齐函数
例如,下面的几种写法都是可以的:

if (veryLongFunctionNameA(
        veryLongArgumentName) ||
    veryLongFunctionNameB(
    veryLongArgumentName)) {
  veryLongFunctionNameC(veryLongFunctionNameD(
      veryLongFunctioNameE(
          veryLongFunctionNameF)));
}

2.5.4 匿名函数的传入
当在参数列表中声明匿名函数时,为了使匿名函数更可读(避免整个函数块显示在右半屏幕),
匿名函数体应当从调用语句的位置起再缩进2个字符位,
或者从函数的声明位置起缩进2个字符位。
例如:

// 匿名函数在调用语句的同一行中声明
prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
  if (a1.equals(a2)) {
    someOtherLongFunctionName(a1);
  } else {
    andNowForSomethingCompletelyDifferent(a2.parrot);
  }
});

// 匿名函数另起一行声明
var names = prefix.something.myExcellentMapFunction(
    verboselyNamedCollectionOfItems,
    function(item) {
      return item.name;
    });

2.5.5 更多的缩进
实际上除了数组/对象的初始化以及传递匿名函数外,所有的换行都应该:
要不左对齐上一行的表达式,
要不缩进4个字符。而不是缩进2个字符。

例如:

someWonderfulHtml = '' +
                    getEvenMoreHtml(someReallyInterestingValues, moreValues,
                                    evenMoreParams, 'a duck', true, 72,
                                    slightlyMoreMonkeys(0xfff)) +
                    '';

thisIsAVeryLongVariableName =
    hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine();

thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() +
    thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore();

someValue = this.foo(
    shortArg,
    'Some really long string arg - this is a pretty common case, actually.',
    shorty2,
    this.bar());

if (searchableCollection(allYourStuff).contains(theStuffYouWant) &&
    !ambientNotification.isActive() && (client.isAmbientSupported() ||
                                        client.alwaysTryAmbientAnyways())) {
  ambientNotification.activate();
}

2.5.6 空白行
使用空白行将逻辑上相关的代码分组。
例如:

doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);

nowDoSomethingWith(y);

andNowWith(z);

2.5.7 二元和三元运算符
总是将运算符集中在一行,这样就不用考虑解析器隐含插入分号的问题。
否则换行和缩进就要遵守上面的规则。

var x = a ? b : c;  // 如果写得下,都写在一行

// 缩进4个字符位也可以
var y = a ?
    longButSimpleOperandB : longButSimpleOperandC;

// 或者缩进时对齐第一个操作数
var z = a ?
        moreComplicatedB :
        moreComplicatedC;

2.6 圆括号
仅在语法或语义需要时候使用。

永远不要为一元运算符例如delete、typeof、void使用圆括号。
也不要在关键字例如return、throw、case、in、new后面使用圆括号。

2.7 字符串
选择使用单引号「’」。
为了保持兼容,推荐选择「’」而不是「”」,在创建包含HTML的字符串时更容易:

var msg = 'This is some HTML';

2.8 可见性(私有域和受保护域)
推荐使用JSDoc的@private和@protected标签,以指示类、函数和属性的可见性。

在使用Closure Compiler编译时,参数「–jscomp_warning=visibility」可以让编译器检测到违反可见性时发出警告。

标记为@private的全局变量和函数仅允许被相同文件中的代码使用。

构造函数如果以@private标记,即意味着只能在同一文件中创建类实例时被调用,
或者在实例化类的静态实例成员时被调用。(防止类在文件外被实例化)
同一文件中的静态属性和instanceof操作符也仍可访问构造函数。

全局的变量/函数/构造函数不应该标记@protected。

// File 1.
// AA_PrivateClass_ and AA_init_ are accessible because they are global
// and in the same file.

/**
 * @private
 * @constructor
 */
AA_PrivateClass_ = function() {
};

/** @private */
function AA_init_() {
  return new AA_PrivateClass_();
}

AA_init_();

@private的属性可被同一文件的任何代码访问,同时也可被拥有这个属性的类的静态方法或实例方法所访问(跨文件时)。
但不能在另外一个文件中被子类所访问或复写。

@protected的属性可被同一文件的任何代码访问,同时也可被派生子类的静态方法或实例方法所访问。

注意JavaScript的这些标签和C++/Java不同。
JavaScript的private和protected允许从同一文件中的任何地方访问,不仅仅是同一个类内。
同样,和C++不一样的是,private的属性不允许被子类复写。

// File 1.

/** @constructor */
  AA_PublicClass = function() {
};

/** @private */
AA_PublicClass.staticPrivateProp_ = 1;

/** @private */
AA_PublicClass.prototype.privateProp_ = 2;

/** @protected */
AA_PublicClass.staticProtectedProp = 31;

/** @protected */
AA_PublicClass.prototype.protectedProp = 4;

// File 2.

/**
 * @return {number} The number of ducks we've arranged in a row.
 */
AA_PublicClass.prototype.method = function() {
  // Legal accesses of these two properties.
  return this.privateProp_ + AA_PublicClass.staticPrivateProp_;
};

// File 3.

/**
 * @constructor
 * @extends {AA_PublicClass}
 */
AA_SubClass = function() {
  // Legal access of a protected static property.
  AA_PublicClass.staticProtectedProp = this.method();
};
goog.inherits(AA_SubClass, AA_PublicClass);

/**
 * @return {number} The number of ducks we've arranged in a row.
 */
AA_SubClass.prototype.method = function() {
  // Legal access of a protected instance property.
  return this.protectedProp;
};

注意在JavaScript中,没法分别一个类型(例如AA_PrivateClass_)和这个类型的构造函数。
也没有方法表明一个类型是公有的,而同时它的构造函数是私有的。
(因为给构造函数起个别名就可以轻易绕过编译器的安全性检查)

注:
JavaScript本身不支持私有、保护、公有机制,如果不是利用闭包来实现,类的成员都是直接可达的。
在注释中添加JSDoc中约定的标签后,一些编译器可以实现安全性检查,以使得代码更加安全稳定。
但事实上浏览器的JS解析器仍然会忽略所有注释。添加约束标签是为了让开发者遵守约定。
这种保护私有域的书写方式和闭包方式是完全不同的。
阅读全文…

6,190 次浏览 | 没有评论
2012年2月27日 | 归档于 程序

修改Flash MP3 Player JW新添加曲目的插入位置

估计有不少人使用Flash MP3 Player JW作为wordpress博客的播放插件。
xml方式配置播放列表(dewplayer同样支持),另外拖曳式编辑播放列表也算是这个插件的一个特点

余希望新曲插入到播放列表头部, 但插件只支持插入到尾部,再通过拖曳调整,当曲目列表太庞大时很烦,拖半天都拖不到头部

于是暴力修改源码

打开插件下的inc/class.playlist_editor.php,搜索“add new song”,会找到相关代码段
再定位到 (2.3版本是539行)

$list_item.appendTo($songs_list);

很简单,将appendTo函数改成prependTo函数。这些函数是jQuery库操纵DOM元素的函数。

不过暴力修改代码之后想直接插入到尾部就不行了,比较好的方法是加多一个新添加曲目插入位置的配置项
余现在只是用Flash MP3 Player JW作为侧边栏的widget,整碟试听的播放列表如果用Flash MP3 Player JW来编辑很痛苦,因此余不需要新添加曲目插入到尾部的功能。本着够用就是好的原则,这次于是就这样了

整碟试听的播放列表现在是用记事本之类的工具编辑,但已经很烦了,准备写个程序为dewplayer自动生成播放列表

4,379 次浏览 | 没有评论
2012年2月27日 | 归档于 技术

Google JavaScript Style Guide 中文简要翻译 – Language Rules部分

组内计划依照Google的JavaScript编程规约来进行开发。非常认真的学习了一下,于是顺手简单地翻译成了中文。
Style Rules部分等有空再翻。因为是原创,虽然可能别人也翻译过了,但转载时仍请注明出处。

余刚学习JavaScript不久,不可避免对原文的理解有错误,如有错误或不当地方请指出。

Google JavaScript Style Guide 中文简要翻译

1. Language Rules部分

1.1 var
总是使用var声明变量。(理由不多说了)

1.2 Constants
使用大写字母和下划线『_』来声明常量。可以在注释中适当使用@const标签。
但不要使用const限定词来修饰变量。IE浏览器不会解析const。
例如,对于简单的类型,命名规则已经足够:

/**
 * The number of seconds in a minute.
 * @type {number}
 */
goog.example.SECONDS_IN_A_MINUTE = 60;

对于复杂类型,使用@const标签:

/**
 * The number of seconds in each of the given units.
 * @type {Object.<number>}
 * @const
 */
goog.example.SECONDS_TABLE = {
  minute: 60,
  hour: 60 * 60
  day: 60 * 60 * 24
}

这允许编译器强制检查是否有改变。

1.3 Semicolons
总是使用分号『;』来结束语句。(理由也不多说了)

1.4 Nested functions
嵌套函数(函数体内定义的函数):可以使用。
嵌套函数有时候非常有用,自己决定在需要时使用。

1.5 Function Declarations Within Blocks
在块中定义函数:不要这样干。
虽然大多数JS解析器支持,但这不是ECMAScript标准。
ECMAScript标准只允许在脚本的全局环境或者函数体内定义函数。
请使用匿名函数并赋值给一个变量来替代。

错误的用法:

if (x) {
  function foo() {}
}

正确的用法:

if (x) {
  var foo = function() {}
}

1.6 Exceptions
应当使用异常处理。异常是不可避免的。

1.7 Custom exceptions
自定义异常:可以使用。自己决定在需要时使用。

1.8 Standards features
为了保持最大兼容,使用标准特性而不要使用非标准特性。
例如使用string.charAt(3)而不是使用string[3]。
又如使用DOM的函数来操作HTML元素而不是使用特定的省略记法。

1.9 Wrapper objects for primitive types
包装内置数据类型:没有理由这样干,而且这样做很危险。

不要这样干:

var x = new Boolean(false);
if (x) {
  alert('hi');  // Shows 'hi'.
}

但类型转换是OK的:

var x = Boolean(0);
if (x) {
  alert('hi');  // This will never be alerted.
}
typeof Boolean(0) == 'boolean';
typeof new Boolean(0) == 'object';

1.10 Multi-level prototype hierarchies
(自定义的)多层原型继承体系:不推荐。
如果你以自定义的类B为原型导出类D,那么你就构造了一个多层原型继承体系。
这些体系比它们乍看起来要难正确使用。

所以,使用Google的Closure库(the Closure Library)中的goog.inherits()或类似的类库来实现。

function D() {
  goog.base(this)
}
goog.inherits(D, B);

D.prototype.method = function() {
  ...
};

注:过长的原型链同时会导致性能问题。

1.11 Method definitions
将方法定义为原型方法。
原型方法定义的例子:

Foo.prototype.bar = function() { ... };

当有多个原型方法要添加时:

Foo.prototype.bar = function() {
  /* 注释 */
};

注:原型方法比构造函数中定义的对象方法效率要高
例如1.11-1的getFullName效率不如1.11-2的getFullName。
1.11-1

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

    this.getFullName = function() {
        return this.firstName + " " + this.lastName;
    };
}

1.11-2

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

Person.prototype.getFullName = function() {
    return this.firstName + " " + this.lastName;
};

1.12 Closures
闭包:可以使用,但要慎重。
这里有一份闭包指南:http://jibbering.com/faq/notes/closures/

要注意到的一点是,一个闭包函数会在它的作用域内保留一个指针。
如果将一个闭包函数赋给DOM元素,会引起循环引用,并导致内存泄漏。例如:

function foo(element, a, b) {
  element.onclick = function() { /* uses a and b */ };
}

闭包函数会保持对element,a和b的引用,即使它从来不使用element。而element也保持对闭包的引用,这构成了一个循环,占用的内存将不会被回收。

应当使用下面的方式代替:

function foo(element, a, b) {
  element.onclick = bar(a, b);
}

function bar(a, b) {
  return function() { /* uses a and b */ }
}

注:闭包可以轻易产生循环引用,导致闭包和在闭包作用域链上的变量都不会被释放。这也正是能够通过闭包方式实现类私有成员的方法之一。对于不涉及DOM/ActiveX元素的闭包,在代码执行完毕后循环引用可以被JS引擎检测到,从而成功释放内存。当加入DOM/ActiveX后,会截断JS引擎的检测,导致内存泄漏。

阅读全文…

6,702 次浏览 | 2 条评论
2012年2月6日 | 归档于 程序

ExtJS4本地化

工作后干的活有点乱七八糟,被折磨死
每一段时间就要学新的语言。2011下半年是C#,2012年,轮换到了JavaScript

最近的任务是用ExtJS设计前端,这玩意强大到足以取代Silverlight,非常适合配合RESTful API使用,使用AJAX获取JSON或XML类型的数据,前端页面的生成完全不需要PHP/JSP,仅HTML+JS已经足够。这种情况下,前端可以和API所在服务端完全分离,部署在不同的服务器上,甚至前端可以放在用户本地运行

第一个任务是攻克多语言化(老大乃将这种任务扔给素人情何以堪)
网上搜索了一下,还有人专门写了插件(ext-locale-loader),但这种需要给每一自设计的页面弄一份语言拷贝的方式让余菊花一紧
后来阅读ExtJS的自带文档,发现有本地化的详细指引($EXTJS_FOLDER/docs/index.html#!/guide/localization)
一步步来就实现了ExtJS自身的UI元件在用户选择不同语言时的本地化

实现后的效果

演示页面:extlocalize.html
ExtJS语言列表RawData:languages.js(仅保留4个语言)
逻辑+UI:extlocalize.js
切换语言的逻辑其实非常简单,判断页面的传入参数(没错,静态页面也可以有参数),利用AJAX加载语言文件,然后执行语言文件中的代码,更新字符串

extlocalize.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Localization example</title>
    <!-- Ext Library Files -->
    <link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css">
    <script src="ext/ext-all-debug.js"></script>
    <!-- App Scripts -->
    <script src="languages.js"></script>
    <script src="extlocalize.js"></script>
</head>
<body>
    <div id="languages"></div>
    <div id="datefield"></div>
    <div id="emailfield"></div>
    <div id="grid"></div>
</body>
</html>

languages.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
Ext.namespace('Ext.local');

Ext.local.languages = [
    ['en', 'English'],
    ['ja', 'Japanese(日本語)'],
    ['zh_CN', 'Simplified Chinese(简体中文)'],
    ['zh_TW', 'Traditional Chinese(繁體中文)']
];

extlocalize.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext.ux', 'ext/examples/ux/');
Ext.require([
    'Ext.data.*',
    'Ext.tip.QuickTipManager',
    'Ext.form.*',
    'Ext.ux.data.PagingMemoryProxy',
    'Ext.grid.Panel'
]);

Ext.onReady(function() {

    MultiLangDemo = (function() {
        return {
            init: function() {
            	var store = Ext.create('Ext.data.ArrayStore', {
            	    fields: ['code', 'language'],
            	    data  : Ext.local.languages //from languages.js
            	});

            	var combo = Ext.create('Ext.form.field.ComboBox', {
            	    renderTo: 'languages',
            	    margin: '10, 0, 0, 10',
            	    store: store,
            	    displayField: 'language',
            	    queryMode: 'local',
            	    emptyText: 'Select a language...',
            	    hideLabel: true,
            	    width: 200,
            	    listeners: {
            	        select: {
            	            fn: function(cb, records) {
            	                var record = records[0];
            	                window.location.search = Ext.urlEncode({"lang":record.get("code")});
            	            },
            	            scope: this
            	        }
            	    }
            	});
            	
            	var params = Ext.urlDecode(window.location.search.substring(1));

            	if (params.lang) {
            	    var url = Ext.util.Format.format('ext/locale/ext-lang-{0}.js', params.lang);

            	    Ext.Ajax.request({
            	        url: url,
            	        success: this.onSuccess,
            	        failure: this.onFailure,
            	        scope: this
            	    });

            	    // check if there's really a language with passed code
            	    var record = store.findRecord('code', params.lang, null, null, null, true);
            	    // if language was found in store, assign it as current value in combobox

            	    if (record) {
            	        combo.setValue(record.data.language);
            	    }
            	} else {
            	    // no language found, default to english
            	    this.setup();
            	}

            	Ext.tip.QuickTipManager.init();
            },
            onSuccess: function(response) {
                try {
                    eval(response.responseText);
                } catch (e) {
                    Ext.Msg.alert('Failure', e.toString());
                }
                this.setup();
            },
            onFailure: function() {
                Ext.Msg.alert('Failure', 'Failed to load locale file.');
                this.setup();
            },
            setup: function() {
                Ext.create('Ext.FormPanel', {
                    renderTo: 'datefield',
                    margin: '10, 0, 0, 10',
                    frame: true,
                    title: 'Date picker',
                    width: 380,
                    defaultType: 'datefield',
                    items: [{
                        fieldLabel: 'Date',
                        name: 'date'
                    }]
                });
                Ext.create('Ext.FormPanel', {
                    renderTo: 'emailfield',
                    margin: '10, 0, 0, 10',
                    labelWidth: 100,
                    frame: true,
                    title: 'E-mail Field',
                    width: 380,
                    defaults: {
                        msgTarget: 'side',
                        width: 340
                    },
                    defaultType: 'textfield',
                    items: [{
                        fieldlabel: 'Email',
                        name: 'email',
                        vtype: 'email'
                    }]
                });

                var monthArray = Ext.Array.map(Ext.Date.monthNames, function (e) { return [e]; });
                var ds = Ext.create('Ext.data.Store', {
                     fields: ['month'],
                     remoteSort: true,
                     pageSize: 6,
                     proxy: {
                         type: 'pagingmemory',
                         data: monthArray,
                         reader: {
                             type: 'array'
                         }
                     }
                 });

                Ext.create('Ext.grid.Panel', {
                    renderTo: 'grid',
                    margin: '10, 0, 0, 10',
                    width: 380,
                    height: 203,
                    title:'Month Browser',
                    columns:[{
                        text: 'Month of the year',
                        dataIndex: 'month',
                        width: 240
                    }],
                    store: ds,
                    bbar: Ext.create('Ext.toolbar.Paging', {
                        pageSize: 6,
                        store: ds,
                        displayInfo: true
                    })
                });
                // trigger the data store load
                ds.load();
            }
        };
    })();

    MultiLangDemo.init();
});

以上仅是ExtJS UI自身的本地化。

自己系统中的文字如何本地化呢?
余的做法基本是沿着ExtJS本地化的思路,将系统用到的字符串集中在单个文件中,这也有利于后期扩展更多的语言
像Date picker/Email Field/Month Browser/Month of the year就是系统自身的语言,不应该硬编码到UI中
在加载ExtJS语言文件的时候,同时也加载系统自身的语言文件进行刷新
系统语言文件也必须使用和ExtJS语言文件相同的后缀,如en/ja/zh_CN/zh_TW

效果:

演示页面:multiplelanguages.html
逻辑+UI:multiplelanguages.js
语言列表RawData:languages.js(和上面一样)
系统默认语言文件(必须):myproject-js/myproject-lang.js(余将默认语言弄成了日文)
系统的本地化语言文件(可选):(没有参数时会保留默认语言文件中的设定)
locale/myproject-lang-en.js
locale/myproject-lang-ja.js
locale/myproject-lang-zh_CN.js
locale/myproject-lang-zh_TW.js

multiplelanguages.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Localization example</title>
    <!-- Ext Library Files -->
    <script src="myproject-js/myproject-lang.js"></script>
    <link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css">
    <script src="ext/ext-all-debug.js"></script>
    <!-- App Scripts -->
    <script src="languages.js"></script>
    <script src="multiplelanguages.js"></script>
</head>
<body>
    <div id="languages"></div>
    <div id="datefield"></div>
    <div id="emailfield"></div>
    <div id="grid"></div>
</body>
</html>

multiplelanguages.js
将UI的生成全部集中到setup函数里了

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
Ext.Loader.setConfig({
    enabled: true
});
Ext.Loader.setPath('Ext.ux', 'ext/examples/ux/');
Ext.require(['Ext.data.*', 'Ext.tip.QuickTipManager', 'Ext.form.*', 'Ext.ux.data.PagingMemoryProxy', 'Ext.grid.Panel']);

Ext.onReady(function() {

    var params;
    MultiLangDemo = (function() {
        return {
            init: function() {
                // load ExtJS locale
                params = Ext.urlDecode(window.location.search.substring(1));
                if (params.lang) {
                    var url = Ext.util.Format.format('ext/locale/ext-lang-{0}.js', params.lang);
                    Ext.Ajax.request({
                        url: url,
                        success: this.onLoadExtLocaleSuccess,
                        failure: this.onLoadExtLocaleFailure,
                        scope: this
                    });
                } else {
                    // no language found, locale of ExtJS will be english as default
                    this.loadmyprojectLocale();
                }
            },
            onLoadExtLocaleSuccess: function(response) {
                try {
                    eval(response.responseText);
                } catch (e) {
                    Ext.Msg.alert('Failure', e.toString());
                }
                this.loadmyprojectLocale();
            },
            onLoadExtLocaleFailure: function() {
                Ext.Msg.alert('Failure', 'Failed to load locale file.');
                this.loadmyprojectLocale();
            },
            loadmyprojectLocale: function() {
                // load locale for myproject
                if (params.lang) {
                    var urlmyprojectLocale = Ext.util.Format.format('locale/myproject-lang-{0}.js', params.lang);
                    Ext.Ajax.request({
                        url: urlmyprojectLocale,
                        success: this.onLoadmyprojectLocaleSuccess,
                        failure: this.onLoadmyprojectLocaleFailue,
                        scope: this
                    });
                } else {
                    this.setup();
                }
            },
            onLoadmyprojectLocaleSuccess: function(response) {
                try {
                    eval(response.responseText);
                } catch (e) {
                    Ext.Msg.alert('Failure', e.toString());
                }
                this.setup();
            },
            onLoadmyprojectLocaleFailue: function() {
                Ext.Msg.alert('Failure', 'Failed to load myproject locale file.');
                this.setup();
            },
            setup: function() {
                var store = Ext.create('Ext.data.ArrayStore', {
                    fields: ['code', 'language'],
                    data: Ext.local.languages //from languages.js
                });

                var combo = Ext.create('Ext.form.field.ComboBox', {
                    renderTo: 'languages',
                    margin: '10, 0, 0, 10',
                    store: store,
                    displayField: 'language',
                    queryMode: 'local',
                    emptyText: myproject.Message.SelectALanguage,
                    hideLabel: true,
                    width: 200,
                    listeners: {
                        select: {
                            fn: function(cb, records) {
                                var record = records[0];
                                window.location.search = Ext.urlEncode({
                                    "lang": record.get("code")
                                });
                            },
                            scope: this
                        }
                    }
                });
                if (params.lang) {
                    // check if there's really a language with passed code
                    var record = store.findRecord('code', params.lang, null, null, null, true);
                    // if language was found in store, assign it as current value in combobox
                    if (record) {
                        combo.setValue(record.data.language);
                    }
                }

                Ext.create('Ext.FormPanel', {
                    renderTo: 'datefield',
                    margin: '10, 0, 0, 10',
                    frame: true,
                    title: myproject.Message.PickDate,
                    width: 380,
                    defaultType: 'datefield',
                    items: [{
                        fieldLabel: myproject.Message.Date,
                        name: 'date'
                    }]
                });
                Ext.create('Ext.FormPanel', {
                    renderTo: 'emailfield',
                    margin: '10, 0, 0, 10',
                    labelWidth: 100,
                    frame: true,
                    title: myproject.Message.EmailFieldTitle,
                    width: 380,
                    defaults: {
                        msgTarget: 'side',
                        width: 340
                    },
                    defaultType: 'textfield',
                    items: [{
                        fieldlabel: 'Email',
                        name: 'email',
                        vtype: 'email'
                    }]
                });

                var monthArray = Ext.Array.map(Ext.Date.monthNames, function(e) {
                    return [e];
                });
                var ds = Ext.create('Ext.data.Store', {
                    fields: ['month'],
                    remoteSort: true,
                    pageSize: 6,
                    proxy: {
                        type: 'pagingmemory',
                        data: monthArray,
                        reader: {
                            type: 'array'
                        }
                    }
                });

                Ext.create('Ext.grid.Panel', {
                    renderTo: 'grid',
                    margin: '10, 0, 0, 10',
                    width: 380,
                    height: 203,
                    title: myproject.Message.MonthList,
                    columns: [{
                        text: myproject.Message.MonthTitle,
                        dataIndex: 'month',
                        width: 240
                    }],
                    store: ds,
                    bbar: Ext.create('Ext.toolbar.Paging', {
                        pageSize: 6,
                        store: ds,
                        displayInfo: true
                    })
                });
                // trigger the data store load
                ds.load();
            }
        };
    })();

    MultiLangDemo.init();
});

myproject-js/myproject-lang.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
var myproject = {};
myproject.Message = {};
myproject.Message.SelectALanguage = '言語を選択ください...';
myproject.Message.PickDate = '日付を選択';
myproject.Message.Date = '日付';
myproject.Message.EmailFieldTitle = 'メールアドレス';
myproject.Message.MonthList = '月の一覧';
myproject.Message.MonthTitle = '月順';

locale/myproject-lang-en.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = 'Select a language...';
	myproject.Message.PickDate = 'Date Picker';
	myproject.Message.Date = 'Date';
	myproject.Message.EmailFieldTitle = 'Email';
	myproject.Message.MonthList = 'Month Browser';
	myproject.Message.MonthTitle = 'Month of the year';
}

locale/myproject-lang-ja.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = '言語を選択ください...';
	myproject.Message.PickDate = '日付を選択';
	myproject.Message.Date = '日付';
	myproject.Message.EmailFieldTitle = 'メールアドレス';
	myproject.Message.MonthList = '月の一覧';
	myproject.Message.MonthTitle = '月順';
}

locale/myproject-lang-zh_CN.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = '请选择一种语言...';
	myproject.Message.PickDate = '选择日期';
	myproject.Message.Date = '日期';
	myproject.Message.EmailFieldTitle = '电子邮件地址';
	myproject.Message.MonthList = '月份一览';
	myproject.Message.MonthTitle = '月份';
}

locale/myproject-lang-zh_TW.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = '請選擇一種語言...';
	myproject.Message.PickDate = '選擇日期';
	myproject.Message.Date = '日期';
	myproject.Message.EmailFieldTitle = '電子郵件地址';
	myproject.Message.MonthList = '月份一覽';
	myproject.Message.MonthTitle = '月份';
}

注意事项:
1.ExtJS库的解压目录名要一致,代码中的为ext
2.由于AJAX的本地请求会因为安全问题被浏览器禁止,需要将文件放到服务器才能测试

源文件下载:localize.rar

8,484 次浏览 | 没有评论
2012年2月3日 | 归档于 技术, 程序

C81战果

早上6点半才起来,基本上和C79同样的时间去到会场排队
今年的同音摊位好少,在西2逛几圈就能觅完,西2还有一堆是卖画册和周边的
根绝风筝酱提供的摊位消息,少女病和六弦alice在同一个摊位,
余虽然觉得很狐疑,但还是上前去询问,结果被回答不是的哦(囧rzorz
有少量团队伍超长,但名字居然没听过Orz(塩と胡椒/蛇下呂,这是卖CD的嘛?
感觉kaede.org简直就卖不动啊

去企业馆又逛了一下,路上遇到淫兽卖萌(对面的兄弟对不住了,码懒得打了

一日目企业馆的贞操还在大腿上,三日目就全都掉到了地板,エロすぎる!
Cosplay没有兴趣去拍,闪人回家吃饭了(企业馆的妹子都比外面广场的Cosplay有爱得多

战果,于是基本上一部psv就木有了cry

6,576 次浏览 | 3 条评论
2011年12月31日 | 归档于 私语
标签:

Java获取网卡接口名称的乱码问题

大概文字编码处理得多了,连同事遇到这方面问题,都跑过来问余了。
今晚同事遇到获取网卡接口名称乱码,无论怎么转都无法解决。
果然最好的方法还是查看字符串的二进制内容,乱猜是无用的。

平台是日文正版xp
NetworkInterface的getDisplayName()方法返回值是String,那确定就是一个Unicode字符串了
直接打印这个名称,最后一个”-“之后的字符串都不正常

使用String的getBytes(“UTF-16”)方法取出字串的二进制内容,全部打印出来,前几个以及最后几个的值如下

-2
-1
0
73  // -> I
0
110  // -> n
0
116  // -> t
0
101  // -> e
0
108  // -> l
...
...
...
0
-125
0
80
0
-125
0
87

首两个字符是-2,-1,也即0xFE,0xFF,大尾的BOM(Java你到底有多变态),每个字符都是高位在前
ASCII部分的字符高位为0,毫无疑问开头的几个字符就是Intel

来看最后几个字符
去掉0之后,序列是-125 80 -125 87,也即0x83 0x50 0x83 0x57,酷似多字节编码
打开WinHex(D版UltraEdit是不能装的),新建一个四字节文件,多字节编码在文本中都是先高位后低位,
于是按顺序写入16进制值,保存为txt文件
用记事本打开txt,结果是”ケジ”(日文版xp)
还可以用Ansi2Unicode打开,选择其他编码查看转换结果,Shift-JIS是最正常的了

于是终于可以下结论,这个字符串的原始编码就是Shift-JIS,但被分开成一个个byte,高位填充0,暴力转换为Unicode字符串。
这样的字符串非常的不正常,难怪怎么转都不对了

还原方法:去掉头部的BOM,去掉所有的0,得到二进制数组,在new一个String的同时,指定这个二进制数组的编码是Shift-JIS

import java.io.UnsupportedEncodingException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

public class hello {

	public static void main(String[] args) {
		System.out.println("日本語");
		try {
			Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
			while (interfaces.hasMoreElements()) {
				NetworkInterface ni = (NetworkInterface)interfaces.nextElement();
				byte[] b = ni.getDisplayName().getBytes("UTF-16");
				byte[] dst = new byte[b.length];
				for (int i=0; i<dst.length; i++) {
					dst[i] = 0;
				}
				int k=0;
				for (int i=0; i<b.length; i++) {
					if ((b[i]!=0) && (b[i]!=-1) && (b[i]!=-2)) {
						dst[k] = b[i];
						k++;
					}
				}
				String name = new String(dst, "Shift_JIS").trim();
				System.out.println(b.length);
				System.out.println(ni.getDisplayName());
				System.out.println(name);
			}
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

猜测是JDK调用了win32 api,却没有处理返回来的字符串编码
同事说Win7平台上没有问题,不需要转换,可能是因为win7系统的api返回的是Unicode字符串
这个修正方法和平台相关,很不通用

5,224 次浏览 | 没有评论
2011年11月17日 | 归档于 程序
标签: , ,

Chinese Converter – 简繁繁简转换程序

稍微花了一点时间写了这个简繁繁简字符转换程序。主要为了验证通用库的扩展能力,程序功能不是目的,因此以后基本不会更新。

程序界面:

特性:
–简繁繁简转换
–仅支持Unicode(LE)输入和Unicode(LE)保存转换结果
–采用维基简繁繁简一对一映射表,如有问题找维基
–不支持词组转换

使用方法很简单,拖曳单个文本到程序窗口,就会自动转换。左上的下拉框可以选择恰当的映射表。

程序下载地址:http://code.google.com/p/unicue/downloads/detail?name=ChineseConverter_1.0.zip

7,258 次浏览 | 2 条评论
2011年11月13日 | 归档于 作品, 程序

猜猜看:哪一种转换方法最快

蛋疼写了三种UTF-16到UTF-8的转换方法。其中一个不出所料果然很慢,但另外的两个测试结果让余跌了一下眼镜。

直接在内存上操作速度当然快,因此converMethod中convert2utf8毫无疑问是最快的
剩下的
convert2utf8_pushback
convert2utf8_copy
convert2utf8_allocate
都是返回STL中的string对象,哪个最快,哪个最慢,大家来猜一下吧

convertMethod.h

#include <string>

bool match(const char *src, unsigned int src_length)
{
	if (NULL == src)
		return false;
	if (src_length == 0)
		return false;
	if ((src_length&1) != 0)
		return false;

	return true;
}

unsigned int calcUtf8StringLength(const char *src, unsigned int src_length, bool is_little_endian)
{
	if (NULL == src)
		return 0;
	if (src_length == 0)
		return 0;
	// if src_length is an odd number
	if ((src_length&1) != 0)
		return 0;

	const unsigned char *unsignedSrc = (unsigned char *)src;
	wchar_t chr = 0;
	unsigned int dest_len = 0;
	for (unsigned int i=0;i<src_length;)
	{
		if (is_little_endian)
			chr = *(unsignedSrc+i) + (*(unsignedSrc+i+1))*256;
		else
			chr = *(unsignedSrc+i+1) + (*(unsignedSrc+i))*256;
		i+=2;

		if (chr <= 0x007F)  // U-0000 - U-007F
		{
			dest_len += 1;
		} else if (chr <= 0x07FF) {  // U-0080 - U-07FF
			dest_len += 2;
		} else if (chr >= 0xD800 && chr <= 0xDFFF) {  // U-D800 - U-DFFF
			dest_len += 0;
		} else {  // U-0800 - UD7FF and UE000 - UFFFF
			dest_len += 3;
		}
	}

	return dest_len;
}

std::string convert2utf8_pushback(const char *src, unsigned int src_length, bool is_little_endian)
{
	std::string utf8str;
	utf8str.clear();

	if (NULL == src)
		return utf8str;
	if (src_length == 0)
		return utf8str;
	// if src_length is an odd number
	if ((src_length&1) != 0)
		return utf8str;

	const unsigned char *unsignedSrc = (unsigned char *)src;
	wchar_t chr = 0;
	char dest_chars[3];
	memset((void*)dest_chars, 0, 3);
	unsigned int dest_len;
	for (unsigned int i=0;i<src_length;)
	{
		dest_len = 0;
		if (is_little_endian)
			chr = *(unsignedSrc+i) + (*(unsignedSrc+i+1))*256;
		else
			chr = *(unsignedSrc+i+1) + (*(unsignedSrc+i))*256;
		i+=2;

		if (chr <= 0x007F)  // U-0000 - U-007F
		{
			dest_len = 1;
			dest_chars[0] = (char)chr;
		} else if (chr <= 0x07FF) {  // U-0080 - U-07FF
			dest_len = 2;
			dest_chars[0] = (char)(0xC0 | (chr>>6));
			dest_chars[1] = (char)(0x80 | (chr&0x003F));
		} else if (chr >= 0xD800 && chr <= 0xDFFF) {  // U-D800 - U-DFFF
			// ignore this unicode character
			dest_len = 0;
		} else {  // U-0800 - UD7FF and UE000 - UFFFF
			dest_len = 3;
			dest_chars[0] = (char)(0xE0 | (chr>>12));
			dest_chars[1] = (char)(0x80 | ((chr>>6) & 0x003F));
			dest_chars[2] = (char)(0x80 | (chr & 0x003F));
		}
		for (unsigned int j=0;j<dest_len;j++)
		{
			utf8str.push_back(dest_chars[j]);
		}
	}

	return utf8str;
}

std::string convert2utf8_copy(const char *src, unsigned int src_length, bool is_little_endian)
{
	if (NULL == src)
		return std::string();
	if (src_length == 0)
		return std::string();
	// if src_length is an odd number
	if ((src_length&1) != 0)
		return std::string();

	unsigned int need_length = calcUtf8StringLength(src, src_length, is_little_endian);
	char* dest = new char[need_length+1];
	char* offset = dest;
	memset((void*)dest, 0, need_length+1);

	const unsigned char *unsignedSrc = (unsigned char *)src;
	wchar_t chr = 0;
	char dest_chars[3];
	memset((void*)dest_chars, 0, 3);
	unsigned int dest_len;
	for (unsigned int i=0;i<src_length;)
	{
		dest_len = 0;
		if (is_little_endian)
			chr = *(unsignedSrc+i) + (*(unsignedSrc+i+1))*256;
		else
			chr = *(unsignedSrc+i+1) + (*(unsignedSrc+i))*256;
		i+=2;

		if (chr <= 0x007F)  // U-0000 - U-007F
		{
			dest_len = 1;
			dest_chars[0] = (char)chr;
		} else if (chr <= 0x07FF) {  // U-0080 - U-07FF
			dest_len = 2;
			dest_chars[0] = (char)(0xC0 | (chr>>6));
			dest_chars[1] = (char)(0x80 | (chr&0x003F));
		} else if (chr >= 0xD800 && chr <= 0xDFFF) {  // U-D800 - U-DFFF
			// ignore this unicode character
			dest_len = 0;
		} else {  // U-0800 - UD7FF and UE000 - UFFFF
			dest_len = 3;
			dest_chars[0] = (char)(0xE0 | (chr>>12));
			dest_chars[1] = (char)(0x80 | ((chr>>6) & 0x003F));
			dest_chars[2] = (char)(0x80 | (chr & 0x003F));
		}
		if (dest_len>0)
		{
			memcpy(offset, dest_chars, dest_len);
			offset+=dest_len;
		}
	}

	std::string utf8str(dest);
	delete []dest;
	return utf8str;
}

std::string convert2utf8_allocate(const char *src, unsigned int src_length, bool is_little_endian)
{
	if (NULL == src)
		return std::string();
	if (src_length == 0)
		return std::string();
	// if src_length is an odd number
	if ((src_length&1) != 0)
		return std::string();

	unsigned int need_length = calcUtf8StringLength(src, src_length, is_little_endian);
	std::string utf8str(need_length, 0);
	unsigned int offset = 0;

	const unsigned char *unsignedSrc = (unsigned char *)src;
	wchar_t chr = 0;
	char dest_chars[3];
	memset((void*)dest_chars, 0, 3);
	unsigned int dest_len;
	for (unsigned int i=0;i<src_length;)
	{
		dest_len = 0;
		if (is_little_endian)
			chr = *(unsignedSrc+i) + (*(unsignedSrc+i+1))*256;
		else
			chr = *(unsignedSrc+i+1) + (*(unsignedSrc+i))*256;
		i+=2;

		if (chr <= 0x007F)  // U-0000 - U-007F
		{
			dest_len = 1;
			dest_chars[0] = (char)chr;
		} else if (chr <= 0x07FF) {  // U-0080 - U-07FF
			dest_len = 2;
			dest_chars[0] = (char)(0xC0 | (chr>>6));
			dest_chars[1] = (char)(0x80 | (chr&0x003F));
		} else if (chr >= 0xD800 && chr <= 0xDFFF) {  // U-D800 - U-DFFF
			// ignore this unicode character
			dest_len = 0;
		} else {  // U-0800 - UD7FF and UE000 - UFFFF
			dest_len = 3;
			dest_chars[0] = (char)(0xE0 | (chr>>12));
			dest_chars[1] = (char)(0x80 | ((chr>>6) & 0x003F));
			dest_chars[2] = (char)(0x80 | (chr & 0x003F));
		}
		for (unsigned int j=0;j<dest_len;j++)
			utf8str[offset++] = dest_chars[j];
	}

	return utf8str;
}

bool convert2utf8(const char *src, unsigned int src_length, char *dest, unsigned int dest_length, bool is_little_endian, bool check_dest_length=false)
{
	if (NULL == src || NULL == dest)
		return false;
	if (0 == src_length || 0 == dest_length)
		return false;
	if (!(dest>src+src_length || src>dest+dest_length))
		return false;
	if ((src_length&1) != 0)
		return false;
	if (check_dest_length)
	{
		if (dest_length < calcUtf8StringLength(src, src_length, is_little_endian))
			return false;
	}

	unsigned int offset = 0;
	const unsigned char *unsignedSrc = (unsigned char *)src;
	wchar_t chr = 0;
	memset((void*)dest, 0, dest_length);
	for (unsigned int i=0;i<src_length;)
	{
		if (is_little_endian)
			chr = *(unsignedSrc+i) + (*(unsignedSrc+i+1))*256;
		else
			chr = *(unsignedSrc+i+1) + (*(unsignedSrc+i))*256;
		i+=2;

		if (chr <= 0x007F)  // U-0000 - U-007F
		{
			*(dest + offset++) = (char)chr;
		} else if (chr <= 0x07FF) {  // U-0080 - U-07FF
			*(dest + offset++) = (char)(0xC0 | (chr>>6));
			*(dest + offset++) = (char)(0x80 | (chr&0x003F));
		} else if (chr >= 0xD800 && chr <= 0xDFFF) {  // U-D800 - U-DFFF
			// ignore this unicode character
		} else {  // U-0800 - UD7FF and UE000 - UFFFF
			*(dest + offset++) = (char)(0xE0 | (chr>>12));
			*(dest + offset++) = (char)(0x80 | ((chr>>6) & 0x003F));
			*(dest + offset++) = (char)(0x80 | (chr & 0x003F));
		}
	}

	return true;
}

测试程序:在VC新建一个工程把代码覆盖进去,并导入convertMethod.h
测试文件的路径需要更改一下,推荐用大文本测试,更能显示出性能的差异
测试文件只能是Unicode编码的文本文件
提供测试样品:bungakusyoujyo-unicode

#pragma once

#include "stdafx.h"
#include <string>
#include <fstream>
#include <iostream>
#include <time.h>
#include "convertMethod.h"

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	string filename = "../testfiles/utf-16/bungakusyoujyo-unicode.txt";
	ifstream infile(filename.c_str(), ios::in|ios::binary);
	if (!infile)
	{
		cerr<<"unable to open input file!\n";
		return -1;
	}
	ofstream outfile("../testfiles/out/bungakusyoujyo-utf-8.txt", ios::binary);
	if (!outfile)
	{
		cerr<<"unable to open output file\n";
		infile.close();
		return -1;
	}

	infile.seekg(0, ios::end);
	unsigned int rawLength = infile.tellg();
	char *rawStringBuffer = new char[rawLength+1];
	memset((void*)rawStringBuffer, 0, rawLength+1);
	infile.seekg(0, 0);
	infile.read(rawStringBuffer, rawLength);
	char *stringBuffer = rawStringBuffer + 2;
	unsigned int length = rawLength - 2;

	bool isLittleEndian=false, hasError=false;
	if (((unsigned char)rawStringBuffer[0]==0xFF)&&((unsigned char)rawStringBuffer[1]==0xFE))
		isLittleEndian = true;
	else if (((unsigned char)rawStringBuffer[1]==0xFF)&&((unsigned char)rawStringBuffer[0]==0xFE))
		isLittleEndian = false;
	else
	{
		hasError = true;
	}

	if (!hasError)
	{
		char UTF_8_BOM[3] = {'\xEF', '\xBB', '\xBF'};
		outfile.write(UTF_8_BOM,3);

		if (match(stringBuffer, length))
		{
			cout<<"Unicode matched!"<<endl;
			// output

			long beginTime, endTime;

			/*method 1*/
			beginTime = clock();
			string& resultBuffer1 = convert2utf8_pushback(stringBuffer, length, isLittleEndian);
			endTime = clock();
			cout<<"utf-8 string length: "<<resultBuffer1.length()<<endl;
			cout<<"pushback method costs time: "<<endTime - beginTime<<"ms"<<endl;

			/*method 2*/
			beginTime = clock();
			string& resultBuffer2 = convert2utf8_copy(stringBuffer, length, isLittleEndian);
			endTime = clock();
			cout<<"utf-8 string length: "<<resultBuffer2.length()<<endl;
			cout<<"copy construct method costs time: "<<endTime - beginTime<<"ms"<<endl;

			/*method 3*/
			beginTime = clock();
			string& resultBuffer3 = convert2utf8_allocate(stringBuffer, length, isLittleEndian);
			endTime = clock();
			cout<<"utf-8 string length: "<<resultBuffer3.length()<<endl;
			cout<<"allocate method costs time: "<<endTime - beginTime<<"ms"<<endl;

			/*method 4*/
			beginTime = clock();
			unsigned int dst_length = calcUtf8StringLength(stringBuffer, length, isLittleEndian);
			char *dst = new char[dst_length];
			bool result = convert2utf8(stringBuffer, length, dst, dst_length, isLittleEndian);
			endTime = clock();
			if (!result)
				cerr<<"Fail to convert to utf-8\n";
			else
			{
				cout<<"utf-8 string length: "<<dst_length<<endl;
				cout<<"memory copy method costs time: "<<endTime - beginTime<<"ms"<<endl;
				/*writing method 1*/
				beginTime = clock();
				outfile.write(dst, dst_length);
				endTime = clock();
				cout<<"writing file by pointer costs time: "<<endTime - beginTime<<"ms"<<endl;
			}
			delete []dst;
			dst = NULL;

			/*writing method 2*/
			/*
			const char *p = resultBuffer1.c_str();
			unsigned int resultLength = resultBuffer1.length();
			beginTime = clock();
			outfile.write(p, resultLength);
			endTime = clock();
			cout<<"writing file by pointer costs time: "<<endTime - beginTime<<"ms"<<endl;
			*/
		
			/*writing method 3*/
			/*
			beginTime = clock();
			outfile<<resultBuffer2;
			endTime = clock();
			cout<<"writing file by stream costs time: "<<endTime - beginTime<<"ms"<<endl;
			*/
		}
	}

	infile.close();
	outfile.close();
	delete []rawStringBuffer;
	rawStringBuffer = NULL;
	stringBuffer = NULL;

	return 0;
}
15,135 次浏览 | 2 条评论
2011年11月8日 | 归档于 技术, 程序
标签: , , ,

三訪秩父 – 龍勢祭り

第一季:https://kuyur.net/blog/archives/1738
第二季:https://kuyur.net/blog/archives/2000

TV Animeあの花中的花火取材自于秩父吉田地区的民俗,龍勢(りゅうぜい)祭り。
龍勢祭り是崎玉县立无形文化财,于每年10月第二个星期日举行。
说是花火,龍勢本质上是火箭。一般的烟花在离开炮筒之后就没有推力,只能靠初速冲到最高点。龍勢靠的自身产生的反冲。
另外在空中产生的缤纷烟雾流是一大特色,虽然也有火药的燃烧,但和一般烟花猛烈爆炸完全不同

10月9日,为了一睹あの花中的花火实景,再一次来到了秩父。这次的照片非常少

早上8点50起来,自从搬家后离池袋已经很远,应该再起早一点就好了(都怪昨晚一直在调教Google Voice)
抵达池袋10点06分,西武铁道前往秩父的特急满席!
只好乘坐普通列车,到了饭能转车的时候居然又等了将近30分钟
这已经决定余赶不上1点钟的由“超平和バスターズ”奉納的第15番火花了

约1点10分的时候,余还在前往龍勢会館的巴士上,即将抵达会馆,然后就在巴士上看着15番花火升上了空
其实30个流派里和动画最神似的不一定是15番,但没听到茅野爱衣现场卖萌,没能近距离目睹这一阿宅盛事,还是非常残念

残念归残念,收拾心情前往花火会场。
在靠近会场时堵得要死,穿过人群终于找到比较好的地点

大家围成一圈(身后是有料席)

龍勢祭り的主角们就不打码了

失败率非常高。余看到3点多,真正成功的只有三个。
像这个没怎么升起就掉到了半山腰

旁边的女孩子们还在担心,咿呀会不会引起火灾之类的

终于拍了一个成功的,也是余所看到的最壮观一个

吊装。在吊装的同时,主持人先会あいさつ,介绍流派,奉纳人和奉纳人的心愿之类。
然后是叙述人的あいさつ和由鼓声伴奏的很搞笑的吟唱。在吟唱快结束时点火

升空

抵达最高点

如同鲜花般盛开

填装在竹竿上的烟雾依次散发

大概是竹竿有降落伞吊着,下降速度比较慢

烟雾随着下降的竹竿拉出彩帘。

阅读全文…

4,549 次浏览 | 没有评论
2011年10月10日 | 归档于 私语

使用service启动tomcat服务导致的log4j输出文字异常

在Amazon的EC2上,用Amazon官方Linux AMI创建了一个实例,安装并配置tomcat,部署应用。
偶然一次使用service tomcat start启动服务,发现log4j输出的log中的日文字符统统变成了问号「?」。但使用/etc/init.d/tomcat启动服务却一切正常。

以前余就知道service方式启动服务会忽略掉很多环境变量,比如JAVA_HOME。
使用man命令查看帮助
[root]# man service

ENVIRONMENT
       LANG, TERM
              The only environment variables passed to the init scripts.

说是service会保留LANG和TERM两个环境变量。(事实上PATH环境变量也会传递进去)

仔细查看service脚本,却完全不是这回事。

[root]# which service
/sbin/service
[root]# vim /sbin/service

cd /
while [ $# -gt 0 ]; do
  case "${1}" in
    --help | -h | --h* )
       echo "${USAGE}" >&2
       exit 0
       ;;
    --version | -V )
       echo "${VERSION}" >&2
       exit 0
       ;;
    *)
       if [ -z "${SERVICE}" -a $# -eq 1 -a "${1}" = "--status-all" ]; then
          cd ${SERVICEDIR}
          for SERVICE in * ; do
            case "${SERVICE}" in
              functions | halt | killall | single| linuxconf| kudzu)
                  ;;
              *)
                if ! is_ignored_file "${SERVICE}" \
                    && [ -x "${SERVICEDIR}/${SERVICE}" ]; then
                  env -i PATH="$PATH" TERM="$TERM" "${SERVICEDIR}/${SERVICE}" status
                fi
                ;;
            esac
          done
          exit 0
       elif [ $# -eq 2 -a "${2}" = "--full-restart" ]; then
          SERVICE="${1}"
          if [ -x "${SERVICEDIR}/${SERVICE}" ]; then
            env -i PATH="$PATH" TERM="$TERM" "${SERVICEDIR}/${SERVICE}" stop
            env -i PATH="$PATH" TERM="$TERM" "${SERVICEDIR}/${SERVICE}" start
            exit $?
          fi
       elif [ -z "${SERVICE}" ]; then
         SERVICE="${1}"
       else
         OPTIONS="${OPTIONS} ${1}"
       fi
       shift
       ;;
   esac
done

if [ -f "${SERVICEDIR}/${SERVICE}" ]; then
   env -i PATH="$PATH" TERM="$TERM" "${SERVICEDIR}/${SERVICE}" ${OPTIONS}
else
   echo $"${SERVICE}: unrecognized service" >&2
   exit 1
fi

本来系统的默认locale是en_US.UTF-8,也即环境变量$LANG的值为en_US.UTF-8,
/etc/init.d/tomcat start方式启动tomcat,除非在启动脚本中干啥坏事,否则全部的环境变量都会保留,log4j输出不会有错误

env -i命令是创建一个空白的运行环境,如果不主动传入参数,所有的环境变量都会被忽略。
Amazon官方Linux AMI的service脚本用env -i创建启动环境时,却删掉了LANG=”$LANG”,也即locale变成了POSIX,log4j输出时按POSIX输出,非ASCII字符全变成了问号

Amazon官方Linux AMI修改自CentOS,但人家CentOS的service脚本LANG保留得好好的
ubuntu的service脚本文件是/usr/sbin/service,LANG同样传递了进去

为了排查这个错误,花了一下午时间,狠命死抽Amazon

改正方法很简单,补回LANG环境变量即可

env -i LANG="$LANG" PATH="$PATH" TERM="$TERM" "${SERVICEDIR}/${SERVICE}" ...

一共四处地方

5,020 次浏览 | 没有评论
2011年10月3日 | 归档于 技术