LoginSignup
2
2

More than 5 years have passed since last update.

Rubyのslim風の表記をHTMLに変換するプログラムをJavaScriptで作った

Last updated at Posted at 2016-10-15

考えたきっかけ

前回の投稿「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に出力されるので、
  |小なり記号は&lt;のように書く。

/で始まる行は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();
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2