waffle
Version:
シンプルなWEBアプリケーションフレームワークです。(ALL YOUR NODE ARE BELONG TO US)
360 lines (314 loc) • 7.53 kB
JavaScript
/*
* Copyright 2012 Katsunori Koyanagi
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
/**
* @overview Javaの標準のプロパティファイル形式であるpropertiesファイルのパーサの実装です。
*/
"use strict";
require("./patch");
var fs = require("fs");
var lang = require("./Lang");
var charMap = (function() {
var obj = {};
for ( var i = 0; i < 16; i++) {
var key = i.toString(16);
obj[key] = obj[key.toUpperCase()] = i;
}
return obj;
})();
/**
* Java形式のプロパティファイルの読み込み及び値の管理の機能を提供します。
*
* @class Java形式のプロパティファイルの読み込み及び値の管理の機能を提供します。
* @constructor
*/
function Properties() {
this.properties = Object.create(null);
}
/**
* 指定のパスのテキストファイルをプロパティとして読み込みます。
*
* @param {String}
* path パス
*/
Properties.prototype.load = function(path) {
var text = require("fs").readFileSync(path, "UTF-8");
this.loadString(text);
};
/**
* 指定のパスのJSONファイルをプロパティとして読み込みます。
*
* @param {String}
* path パス
*/
Properties.prototype.loadJSON = function(path) {
var content = fs.readFileSync(path, 'utf8');
if (content.charCodeAt(0) === 0xfeff) {
content = content.slice(1);
}
var json = JSON.parse(stripBOM(content));
this.loadObject(json);
};
/**
* 指定のオブジェクトをプロパティとして読み込みます。
*
* @param {Object}
* object オブジェクト
*/
Properties.prototype.loadObject = function(object) {
var data = Object.create(null);
flatten("", object, data);
for ( var key in data) {
this.properties[key] = data[key];
}
};
/**
* 管理されているプロパティのキーの配列を返します。
*
* @return {Array} キーの配列
*/
Properties.prototype.keys = function() {
var keys = [];
for ( var key in this.properties) {
keys[keys.length] = key;
}
return keys;
};
/**
* キーに該当する値を取得して返します。
*
* @param {String}
* key キー
* @param {String}
* defaultValue デフォルト値(省略可)
* @return {String} 値、取得できない場合はnullもしくはデフォルト値
*/
Properties.prototype.getString = function(key, defaultValue) {
if (key in this.properties) {
return this.properties[key];
}
return defaultValue || null;
};
/**
* キーに該当する値を存在するかを返します。
*
* @param {String}
* key キー
* @return {Boolean} 存在する場合はtrue
*/
Properties.prototype.hasProperty = function(key) {
return key in this.properties;
};
/**
* 文字列をプロパティとして読み込みます。
*
* @param {String}
* str プロパティを示す文字列
*/
Properties.prototype.loadString = function(str) {
if (!str) {
return;
}
var obj = parseProperties(str);
for ( var key in obj) {
this.properties[key] = obj[key];
}
};
//
// 行の読み込み
//
function LineReader(str) {
this.lines = str.split(/\r\n|\n|\r/);
this.index = 0;
}
LineReader.prototype.next = function() {
if (this.index >= this.lines.length) {
return null;
}
var result = "", i, j;
for (i = this.index, j = this.lines.length; i < j; i++) {
var line = this.lines[i];
var len = line.length;
if (line[len - 1] !== "\\") {
result += line;
break
}
result += line.substr(0, len - 1);
}
this.index = i + 1;
return result;
};
//
// プロパティファイルのパース
//
function parseProperties(str) {
var result = {};
var reader = new LineReader(str);
var line;
while ((line = reader.next()) !== null) {
parseProperty(line, result);
}
return result;
}
//
// 行のパース
//
function parseProperty(line, result) {
var i, j, key, value, ch;
var s = -1;
var e = line.length;
var sp = false;
for (i = 0, j = line.length; i < j; i++) {
ch = line.charCodeAt(i);
if (s === -1) {
if (ch <= 0x20) {
continue;
}
if (ch === 0x23 || ch === 0x21) {
return;
}
if (ch === 0x3d || ch === 0x3a) {
s = i;
e = i + 1;
break;
}
s = i;
} else if (ch <= 0x20 || ch === 0x3d || ch === 0x3a) {
e = i;
break;
}
}
key = line.substring(s, e);
if (key.length > 0) {
key = unescape(key);
}
s = e;
e = line.length;
for (i = s; i < j; i++) {
ch = line.charCodeAt(i);
if (ch <= 0x20) {
s = i + 1;
continue;
}
if (!sp && (ch === 0x3d || ch === 0x3a)) {
sp = true;
s = i + 1;
continue;
}
break;
}
value = line.substring(s, e);
if (value.length > 0) {
value = unescape(value);
}
result[key] = value;
}
//
// 値、キーのエスケープされた形式の復元
//
function unescape(token) {
var result = "";
var index;
var ch;
var offset;
var code;
var digit
var i;
while (true) {
index = token.indexOf("\\");
if (index > -1) {
result += token.substr(0, index);
token = token.substr(index + 1);
offset = 0;
if (token.length > 0) {
ch = token[0];
} else {
break;
}
if (ch === "u" && token.length > 4) {
code = 0;
for (i = 0; i < 4; i++) {
ch = token[i + 1];
if (!ch in charMap) {
code = NaN;
break;
}
code = (code << 4) + charMap[ch];
}
if (!isNaN(code)) {
result += String.fromCharCode(code);
offset += 5;
} else {
result += "u";
offset++;
}
} else {
switch (ch) {
case "t":
result += "\t";
offset++;
break;
case "r":
result += "\r";
offset++;
break;
case "n":
result += "\n";
offset++;
break;
case "f":
result += "\f";
offset++;
break;
default:
result += ch;
offset++;
break;
}
}
token = token.substr(offset);
} else {
break;
}
}
result += token;
return result;
}
//
// 階層化されたオブジェクトをフラットな形式に変換
// キーは階層単位でドット区切りとなる({a : {b : c}}はa.b=cになる)
//
function flatten(name, value, data) {
if (lang.isBoolean(value) || lang.isNumber(value) || lang.isString(value)
|| value == null) {
data[name] = value == null ? "" : String(value);
return;
}
for ( var key in value) {
flatten(name ? (name + "." + key) : key, value[key], data);
}
}
//
// require hook
//
require.extensions[".properties"] = function(module, filename) {
var properties = new Properties();
properties.load(filename);
module.exports = properties.properties;
};
//
// expose
//
module.exports = Properties;