src/senko/Format.js
import System from "./System";
/**
* The script is part of SenkoWSH.
*
* AUTHOR:
* natade (http://twitter.com/natadea)
*
* LICENSE:
* The MIT license https://opensource.org/licenses/MIT
*/
/**
* 書式に合わせて文字列を組み立てる関数を提供するクラス
*/
export default class Format {
/**
* `printf` に似た書式に合わせて文字列を組み立てる
* - ロケール、日付時刻等はサポートしていません。
* - 変換指定子の`p`と`n`はサポートしていません。
* @param {String} text
* @param {...any} parm パラメータは可変引数
* @returns {String}
*/
static textf() {
let parm_number = 1;
const parm = arguments;
/**
* @param {number} x
* @returns {number}
* @private
*/
const toUnsign = function(x) {
if(x >= 0) {
return x;
}
else {
const ix = -x;
//16ビットごとに分けてビット反転
let high = ((~ix) >> 16) & 0xFFFF;
high *= 0x00010000;
const low = (~ix) & 0xFFFF;
return high + low + 1;
}
};
/**
* @param {string} istr
* @returns {string}
* @private
*/
const func = function(istr) {
// 1文字目の%を除去
let str = istr.substring(1, istr.length);
let buff;
// [6] 変換指定子(最後の1文字を取得)
buff = str.match(/.$/);
const type = buff[0];
if(type === "%") {
return("%");
}
// ここからパラメータの解析開始
// [1] 引数順
buff = str.match(/^[0-9]+\$/);
if(buff !== null) {
buff = buff[0];
// 残りの文字列を取得
str = str.substring(buff.length, str.length);
// 数字だけ切り出す
buff = buff.substring(0, buff.length - 1);
// 整数へ
parm_number = parseInt(buff , 10);
}
// 引数を取得
let parameter = parm[parm_number];
if(typeof parameter !== "string" && typeof parameter !== "number") {
parameter = parameter.toString();
}
parm_number = parm_number + 1;
// [2] フラグ
buff = str.match(/^[-+ #0]+/);
let isFlagSharp = false;
let isFlagTextAlignLeft = false;
const isFlagFill = false;
let sFillCharacter = " ";
let isFlagFillZero = false;
let isFlagDrawSign = false;
let sSignCharacter = "";
if(buff !== null) {
buff = buff[0];
// 残りの文字列を取得
str = str.substring(buff.length, str.length);
if(buff.indexOf("#") !== -1) {
isFlagSharp = true;
}
if(buff.indexOf("-") !== -1) {
isFlagTextAlignLeft = true;
}
if(buff.indexOf(" ") !== -1) {
isFlagDrawSign = true;
sSignCharacter = " ";
}
if(buff.indexOf("+") !== -1) {
isFlagDrawSign = true;
sSignCharacter = "+";
}
if(buff.indexOf("0") !== -1) {
isFlagFillZero = true;
sFillCharacter = "0";
}
}
// [3] 最小フィールド幅
let width = 0;
buff = str.match(/^([0-9]+|\*)/);
if(buff !== null) {
buff = buff[0];
// 残りの文字列を取得
str = str.substring(buff.length, str.length);
if(buff.indexOf("*") !== -1) { // 引数で最小フィールド幅を指定
width = parameter;
parameter = parm[parm_number];
parm_number = parm_number + 1;
}
else { // 数字で指定
width = parseInt(buff , 10);
}
}
// [4] 精度の指定
let isPrecision = false;
let precision = 0;
buff = str.match(/^(\.((-?[0-9]+)|\*)|\.)/); //.-3, .* , .
if(buff !== null) {
buff = buff[0];
// 残りの文字列を取得
str = str.substring(buff.length, str.length);
isPrecision = true;
if(buff.indexOf("*") !== -1) { // 引数で精度を指定
precision = parameter;
parameter = parm[parm_number];
parm_number = parm_number + 1;
}
else if(buff.length === 1) { // 小数点だけの指定
precision = 0;
}
else { // 数字で指定
buff = buff.substring(1, buff.length);
precision = parseInt(buff , 10);
}
}
// 長さ修飾子(非サポート)
buff = str.match(/^hh|h|ll|l|L|z|j|t/);
if(buff !== null) {
str = str.substring(buff.length, str.length);
}
// 文字列を作成する
let output = "";
let isInteger = false;
switch(type.toLowerCase()) {
// 数字関連
case "d":
case "i":
case "u":
case "b":
case "o":
case "x":
isInteger = true;
// falls through
case "e":
case "f":
case "g":
{
let sharpdata = "";
let textlength = 0; // 現在の文字を構成するために必要な長さ
let spacesize; // 追加する横幅
// 整数
if(isInteger) {
// 数字に変換
if(isNaN(parameter)) {
parameter = parseInt(parameter, 10);
}
// 正負判定
if((type === "d") || (type === "i")) {
if(parameter < 0) {
sSignCharacter = "-";
parameter = -parameter;
}
parameter = Math.floor(parameter);
}
else {
if(parameter >= 0) {
parameter = Math.floor(parameter);
}
else {
parameter = Math.ceil(parameter);
}
}
}
// 実数
else {
// 数字に変換
if(isNaN(parameter)) {
parameter = parseFloat(parameter);
}
// 正負判定
if(parameter < 0) {
sSignCharacter = "-";
parameter = -parameter;
}
if(!isPrecision) {
precision = 6;
}
}
// 文字列を作成していく
switch(type.toLowerCase()) {
case "d":
case "i":
output += parameter.toString(10);
break;
case "u":
output += toUnsign(parameter).toString(10);
break;
case "b":
output += toUnsign(parameter).toString(2);
if(isFlagSharp) {
sharpdata = "0b";
}
break;
case "o":
output += toUnsign(parameter).toString(8);
if(isFlagSharp) {
sharpdata = "0";
}
break;
case "x":
case "X":
output += toUnsign(parameter).toString(16);
if(isFlagSharp) {
sharpdata = "0x";
}
break;
case "e":
output += parameter.toExponential(precision);
break;
case "f":
output += parameter.toFixed(precision);
break;
case "g":
if(precision === 0) { // 0は1とする
precision = 1;
}
output += parameter.toPrecision(precision);
// 小数点以下の語尾の0の削除
if((!isFlagSharp) && (output.indexOf(".") !== -1)) {
output = output.replace(/\.?0+$/, ""); // 1.00 , 1.10
output = output.replace(/\.?0+e/, "e"); // 1.0e , 1.10e
}
break;
default:
// 上でチェックしているため、ありえない
break;
}
// 整数での後処理
if(isInteger) {
if(isPrecision) { // 精度の付け足し
spacesize = precision - output.length;
for(let i = 0; i < spacesize; i++) {
output = "0" + output;
}
}
}
// 実数での後処理
else {
if(isFlagSharp) {
// sharp指定の時は小数点を必ず残す
if(output.indexOf(".") === -1) {
if(output.indexOf("e") !== -1) {
output = output.replace("e", ".e");
}
else {
output += ".";
}
}
}
}
// 指数表記は、3桁表示(double型のため)
if(output.indexOf("e") !== -1) {
/**
* @param {string} str
* @returns {string}
* @private
*/
const buff = function(str) {
const l = str.length;
if(str.length === 3) { // e+1 -> e+001
return(str.substring(0, l - 1) + "00" + str.substring(l - 1, l));
}
else { // e+10 -> e+010
return(str.substring(0, l - 2) + "0" + str.substring(l - 2, l));
}
};
output = output.replace(/e[+-][0-9]{1,2}$/, buff);
}
textlength = output.length + sharpdata.length + sSignCharacter.length;
spacesize = width - textlength;
// 左よせ
if(isFlagTextAlignLeft) {
for(let i = 0; i < spacesize; i++) {
output = output + " ";
}
}
// 0を埋める場合
if(isFlagFillZero) {
for(let i = 0; i < spacesize; i++) {
output = "0" + output;
}
}
// マイナスや、「0x」などを接続
output = sharpdata + output;
output = sSignCharacter + output;
// 0 で埋めない場合
if((!isFlagFillZero) && (!isFlagTextAlignLeft)) {
for(let i = 0; i < spacesize; i++) {
output = " " + output;
}
}
// 大文字化
if(type.toUpperCase() === type) {
output = output.toUpperCase();
}
break;
}
// 文字列の場合
case "c":
if(!isNaN(parameter)) {
parameter = String.fromCharCode(parameter);
}
// falls through
case "s":
{
if(!isNaN(parameter)) {
parameter = parameter.toString(10);
}
output = parameter;
if(isPrecision) { // 最大表示文字数
if(output.length > precision) {
output = output.substring(0, precision);
}
}
const s_textlength = output.length; // 現在の文字を構成するために必要な長さ
const s_spacesize = width - s_textlength; // 追加する横幅
// 左よせ / 右よせ
if(isFlagTextAlignLeft) {
for(let i = 0; i < s_spacesize; i++) {
output = output + " ";
}
}
else {
// 拡張
const s = isFlagFillZero ? "0" : " ";
for(let i = 0; i < s_spacesize; i++) {
output = s + output;
}
}
break;
}
// パーセント
case "%":
output = "%";
break;
// 未サポート
case "p":
case "n":
output = "(unsupported)";
break;
default:
// 正規表現でチェックしているため、ありえない
break;
}
return (output);
};
return (parm[0].replace(/%[^diubBoxXeEfFgGaAcspn%]*[diubBoxXeEfFgGaAcspn%]/g, func));
}
/**
* 時刻用の書式に合わせて文字列を組み立てる
* - `YYYY-MM-DD hh:mm:ss` のように指定できる。
* @param {String} text
* @param {Date} date 時刻情報
* @returns {String}
*/
static datef(text, date) {
const Y = date.getFullYear();
const M = date.getMonth() + 1;
const D = date.getDate();
const h = date.getHours();
const m = date.getMinutes();
const s = date.getSeconds();
const ms = date.getMilliseconds();
const day = date.getDay(); // 曜日
const aaa_array = [26085, 26376, 28779, 27700, 26408, 37329, 22303];
const aaaa_str = String.fromCharCode(26332) + String.fromCharCode(26085);
const ddd_array = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const dddd_array = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
let output = text;
output = output.replace(/YYYY/g, Y.toString());
output = output.replace(/YY/g, (Y % 100).toString());
output = output.replace(/MM/g, Format.textf("%02d", M));
output = output.replace(/M/g, M.toString());
output = output.replace(/DD/g, Format.textf("%02d", D));
output = output.replace(/D/g, D.toString());
output = output.replace(/hh/g, Format.textf("%02d", h));
output = output.replace(/h/g, h.toString());
output = output.replace(/mm/g, Format.textf("%02d", m));
output = output.replace(/m/g, m.toString());
output = output.replace(/ss/g, Format.textf("%02d", s));
output = output.replace(/s/g, s.toString());
output = output.replace(/000/g, Format.textf("%03d", ms));
output = output.replace(/aaaa/g, String.fromCharCode(aaa_array[day]) + aaaa_str);
output = output.replace(/aaa/g, String.fromCharCode(aaa_array[day]));
output = output.replace(/dddd/g, dddd_array[day]);
output = output.replace(/ddd/g, ddd_array[day]);
output = output.replace(/day/g, day.toString());
return output;
}
}