Rubyのslim風の表記をHTMLに変換するPHPスクリプトを作った

#考えたきっかけ
HTMLを書いたり読んだりするときに<や>がふと邪魔に感じた。

slimという書式があったなと思い出したので、似た書式で書ける変換プログラムを作ってみた。

書式は自分の好みで決めたので、結果的にはslimには少ししか似ていないものになった。

簡単な処理の割に便利なのでJavaScriptに書き換えてnode.jsで動くようにしてみようと思う。

#使い方
この投稿の最後にあるPHPスクリプト(swim.php)をサーバーに置いてから、次のようなファイルを書くとHTMLに変換されて表示される。

```php:index.php
<?php require "swim.php"; ?>
!DOCTYPE html
html lang="ja"
  head
    meta charset="UTF-8"
    title|テストページ
  body
    h1|テストページ
    div
      p|このページはテストページです。
```

```html:結果
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>テストページ</title>
  </head>
  <body>
    <h1>テストページ</h1>
    <div>
      <p>このページはテストページです。</p>
    </div>
  </body>
</html>
```

#書式

HTMLから<と>と閉じタグを取り除き、テキストの前に|を書く。

タグの階層はインデントで表現する。インデントはスペースでもタブでもいい。ただし、インデントにスペースとタブが混在してはいけない。

いくつか短縮形がある。短縮形を活用すると結構短く書けるようになる。

```text:通常形
div class="abc def"|テキスト
```

classは.で表現できる。

```text:短縮形
div.abc.def|テキスト
```

divは省略できる。

```text:さらに短縮形
.abc.def|タイトル
```

テキストを含まない階層はまとめて書ける。

```text:通常形
li
  a href="..."
    span|ここをクリック
```

```text:短縮形
li>a href="...">span|ここをクリック
```

テキストは|に続けて書く。

```text:一行のテキスト
p|テキスト
```

```text:複数行のテキスト
p
  |これがテキスト。
  |続けて書く。
  |そのままHTMLに出力されるので、
  |小なり記号は&lt;のように書く。
```

/で始まる行はHTMLには出力されない。

```text:コメント
/ コメント
```

/?で始まる行はPHPのコードとして実行される。

```text:PHPコード
/? print("OK");
```

#変換スクリプト

```php:swim.php
<?php

function makeHtml() {
	$stack = array();
	$in = fopen($_SERVER["SCRIPT_FILENAME"], "r");
	fgets($in, 4096);
	while (($line = fgets($in, 4096)) !== false) {
		$line = rtrim($line);
		$name = ltrim($line);
		$indent = strlen($line) - strlen($name);
		if (substr($name, 0, 1) == "/") {
			if (substr($name, 1, 1) == "?") {
				eval(substr($name, 2));
			}
			continue;
		}
		$text = "";
		if (strpos($name, "|") !== false) {
			list($name, $text) = explode("|", $name, 2);
		}
		if ($name == "") {
			print($text);
		}
		elseif (count($stack) == 0) {
			$stack[0] = array($indent, $name);
			printNodeOpen($stack, $text);
		}
		elseif ($indent == $stack[0][0]) {
			printNodeClose($stack);
			$stack[0] = array($indent, $name);
			printNodeOpen($stack, $text);
		}
		elseif ($indent > $stack[0][0]) {
			array_unshift($stack, array($indent, $name));
			printNodeOpen($stack, $text);
		}
		else {
			while (count($stack) > 0 && $indent <= $stack[0][0]) {
				printNodeClose($stack);
				array_shift($stack);
			}
			array_unshift($stack, array($indent, $name));
			printNodeOpen($stack, $text);
		}
	}
	while (count($stack) > 0) {
		printNodeClose($stack);
		array_shift($stack);
	}
	fclose($in);
}

function printNodeOpen($stack, $text) {
	$indentLevel = $stack[0][0];
	$tags = explode(">", $stack[0][1]);
	$tag = array_pop($tags);
	for ($i = 0; $i < count($tags); $i++) {
		printNode($indentLevel + $i, $tags[$i], true, "");
	}
	printNode($indentLevel + count($tags), $tag, true, $text);
	if (count($tags) >= 1) {
		printNode($indentLevel + count($tags), $tag, false);
		for ($i = count($tags) - 1; $i >= 1 ; $i--) {
			printNode($indentLevel + $i, $tags[$i], false);
		}
	}
}

function printNodeClose($stack) {
	$indentLevel = $stack[0][0];
	list($tag) = explode(">", $stack[0][1], 2);
	printNode($indentLevel, $tag, false);
}

function printNode($indentLevel, $tag, $isOpen, $value = "") {
	$indent = str_repeat("  ", $indentLevel);
	$attr = "";
	if (strpos($tag, " ") !== false) {
		list($tag, $attr) = explode(" ", $tag, 2);
		if ($attr != "") {
			$attr = " ".$attr;
		}
	}
	$class = "";
	if (strpos($tag, ".") !== false) {
		$classList = explode(".", $tag);
		$tag = array_shift($classList);
		if ($tag == "") {
			$tag = "div";
		}
		if (count($classList) >= 1) {
			$class = " class=\"".implode(" ", $classList)."\"";
		}
	}
	$type = "";
	if (strpos($tag, "-") !== false) {
		list($tag, $type) = explode("-", $tag, 2);
		if ($type != "") {
			$type = " type=\"".$type."\"";
		}
	}
	if (
		$tag == "!DOCTYPE" || $tag == "meta" || $tag == "link" ||
		$tag == "img" || $tag == "input"
	) {
		if ($isOpen) {
			print($indent."<".$tag.$type.$class.$attr.">\n");
		}
	}
	elseif ($isOpen) {
		print($indent."<".$tag.$type.$class.$attr.">\n");
		if ($value != "") {
			print($value."\n");
		}
	}
	else {
		print($indent."</".$tag.">\n");
	}
}

makeHtml();
exit();
```