#考えたきっかけ
前回の投稿「Rubyのslim風の表記をHTMLに変換するPHPスクリプトを作った」で作ったスクリプトが思いのほか便利だったので、JavaScriptで書き直してみた。
#使い方
Node.jsをインストールしたコンピュータで次のように実行すると、ソースファイルがHTMLに変換されて標準出力に出力される。swim.jsはこの投稿の最後にある。
node swim.js ソースファイル
ソースファイルの例
!DOCTYPE html
html lang="ja"
head
meta charset="UTF-8"
title|テストページ
body
h1|テストページ
div
p|このページはテストページです。
変換した結果
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>
テストページ
</title>
</head>
<body>
<h1>
テストページ
</h1>
<div>
<p>
このページはテストページです。
</p>
</div>
</body>
</html>
#書式
HTMLから<と>と閉じタグを取り除き、テキストの前に|を書く。
タグの階層はインデントで表現する。インデントはスペースでもタブでもいい。ただし、インデントにスペースとタブが混在してはいけない。
いくつか短縮形がある。短縮形を活用すると結構短く書けるようになる。
通常形
div class="abc def"|テキスト
classは.で表現できる。
短縮形
div.abc.def|テキスト
divは省略できる。
さらに短縮形
.abc.def|タイトル
テキストを含まない階層はまとめて書ける。
通常形
li
a href="..."
span|ここをクリック
短縮形
li>a href="...">span|ここをクリック
テキストは|に続けて書く。
一行のテキスト
p|テキスト
複数行のテキスト
p
|これがテキスト。
|続けて書く。
|そのままHTMLに出力されるので、
|小なり記号は<のように書く。
/で始まる行はHTMLには出力されない。
コメント
/ コメント
/?で始まる行はJavaScriptのコードとして実行される。
JavaScriptのコード
/? process.stdout.write("OK");
#変換スクリプト
swim.jsは次のとおり。ファイルを監視して、swimファイルが更新されたら自動的にhtmlファイルも更新されるようにしたら便利かも。
swim.js
"use strict";
var Swim = function(source, indentUnit) {
this.source = source;
this.indentUnit = indentUnit === undefined ? " ": indentUnit;
};
Swim.prototype.makeHtml = function() {
var stack = [];
var fs = require("fs");
var self = this;
fs.readFile(this.source,"utf-8", function(error, content) {
if (error) {
console.log(error);
return;
}
var lines = content.split("\n");
for (var i = 0; i < lines.length; i++) {
var line = lines[i].replace(/\s+$/,'');
var name = line.replace(/^\s+/,'');
var indent = line.length - name.length;
if (name.slice(0, 1) == "/") {
if (name.slice(1, 2) == "?") {
eval(name.slice(2));
}
continue;
}
var text = "";
var x = name.indexOf("|");
if (x >= 0) {
text = name.slice(x + 1);
name = name.slice(0, x);
}
if (stack.length == 0) {
stack[0] = [indent, name];
self.printNodeOpen(stack, text);
}
else if (indent == stack[0][0]) {
self.printNodeClose(stack);
stack[0] = [indent, name];
self.printNodeOpen(stack, text);
}
else if (indent > stack[0][0]) {
stack.unshift([indent, name]);
self.printNodeOpen(stack, text);
}
else {
while (stack.length > 0 && indent <= stack[0][0]) {
self.printNodeClose(stack);
stack.shift();
}
stack.unshift([indent, name]);
self.printNodeOpen(stack, text);
}
}
while (stack.length > 0) {
self.printNodeClose(stack);
stack.shift();
}
});
};
Swim.prototype.printNodeOpen = function(stack, text) {
var indentLevel = stack[0][0];
var tags = stack[0][1].split(">");
var tag = tags.pop();
for (var i = 0; i < tags.length; i++) {
this.printNode(indentLevel + i, tags[i], true, "");
}
this.printNode(indentLevel + tags.length, tag, true, text);
if (tags.length >= 1) {
this.printNode(indentLevel + tags.length, tag, false, "");
for (i = tags.length - 1; i >= 1 ; i--) {
this.printNode(indentLevel + i, tags[i], false, "");
}
}
};
Swim.prototype.printNodeClose = function(stack) {
var indentLevel = stack[0][0];
var pair = stack[0][1].split(">");
var tag = pair[0];
this.printNode(indentLevel, tag, false, "");
};
Swim.prototype.printNode = function(indentLevel, tag, isOpen, value) {
var indent = this.indentUnit.repeat(indentLevel);
var attr = "";
var x = tag.indexOf(" ");
if (x >= 0) {
attr = tag.slice(x + 1);
tag = tag.slice(0, x);
if (attr != "") {
attr = " "+attr;
}
}
var className = "";
if (tag.indexOf(".") >= 0) {
var classList = tag.split(".");
tag = classList.shift();
if (tag == "") {
tag = "div";
}
if (classList.length >= 1) {
className = " class=\""+classList.join(" ")+"\"";
}
}
var type = "";
var x = tag.indexOf("-");
if (x >= 0) {
type = tag.slice(x + 1);
tag = tag.slice(0, x);
if (type != "") {
type = " type=\""+type+"\"";
}
}
if (tag == "") {
if (isOpen) {
if (value != "") {
this.print(indent+value+"\n");
}
}
}
else if (
tag == "!DOCTYPE" || tag == "meta" || tag == "link" ||
tag == "img" || tag == "input"
) {
if (isOpen) {
this.print(indent+"<"+tag+type+className+attr+">\n");
}
}
else if (isOpen) {
this.print(indent+"<"+tag+type+className+attr+">\n");
if (value != "") {
this.print(indent+this.indentUnit+value+"\n");
}
}
else {
this.print(indent+"</"+tag+">\n");
}
};
Swim.prototype.print = function(str) {
process.stdout.write(str);
}
if (process.argv.length == 3) {
var source = process.argv[2];
var swim = new Swim(source);
swim.makeHtml();
}