目次

Wikiエンジン、リスト処理まで完成

[ Category : CMS開発プロジェクト ] 2010年01月26日 02:25
 Wikiエンジン自作中です。見出しや文字の修飾、リンク、画像の表示等に加え、リストの処理まで完成しました。

現在の状況

コード

 とりあえずはコードから(汚いですけど)

cording.php
<?php
 
class cording{
 
	public $enable_nesting_tags = array(
		'table'		=> 'tr',
		'tr'			=> array('td', 'th'),
		'td'			=> array('ul', 'ol'),
		'th'			=> array('ul', 'ol'),
		'ul'			=> array('ul', 'ol', 'li'),
		'ol'			=> array('ul', 'ol', 'li'),
		'div'			=> array('p', 'table', 'ul', 'ol', 'h2', 'h3', 'h4', 'h5'),
		'p'				=> array('ul', 'ol')
	);
 
	public $cording_open_tags = array();
	public $cording_now_tag;
	public $cording_mode;
	public $cording_list_array;
	public $cording_last_list_level;
	public $cording_last_list_type;
 
 
	function cording_tag_close_check($tag){
		global $_debug;
		$result = FALSE;
		if (count($this->cording_open_tags) > 0){
			if (is_array($this->enable_nesting_tags[$this->cording_open_tags[0]])){
				if (!in_array($tag, $this->enable_nesting_tags[$this->cording_open_tags[0]])){
					$result = TRUE;
				}
			}else if ($tag != $this->enable_nesting_tags[$this->cording_open_tags[0]]){
				$result = TRUE;
			}
		}
		return $result;
	}
 
	function cording_tag_close($tag, $unshift_flug=TRUE, $reset_list_level=TRUE){
		if ($this->cording_tag_close_check($tag)){
			while($this->cording_tag_close_check($tag)){
				$close_tag = array_shift($this->cording_open_tags);
				echo '</'.$close_tag.">\n";
			}
		}
		if ($unshift_flug){
			array_unshift($this->cording_open_tags, $tag);
		}
		if ($reset_list_level){
			$this->cording_last_list_level = 0;
		}
	}
 
	function cording_list($new_level, $type, $body){
		if (!in_array("p", $this->cording_open_tags)){
			$this->cording_tag_close("p", TRUE, TRUE);
			echo "<p>\n";
		}
		if ($new_level > $this->cording_last_list_level){
			$this->cording_tag_close($type, TRUE, FALSE);
			$str =  '<'.$type.' class=level'.$new_level.'">'."\n".'<li class="level_'.$new_level.'">'.$this->cording_text($body)."</li>\n";
		}else if ($new_level < $this->cording_last_list_level){
			$str =  '<'.$this->cording_last_list_type.">\n".'<li class="level_'.$new_level.'">'.$this->cording_text($body)."</li>\n";
		}else{
			$str =  '<li class="level_'.$new_level.'">'.$this->cording_text($body)."</li>\n";
		}
		$this->cording_last_list_level = $new_level;
		$this->cording_last_list_type = $new_type;
 
		return $str;
	}
 
	function cording_text($str){
		global $a_target;
		$str = preg_replace("/<([^\.=]*)>/", "&lt;$1&gt;", $str);
		$str = preg_replace("/\*\*([^\*]*)\*\*/", "<b>$1</b>", $str);
		$str = preg_replace("/\/\/([^\/\[\]\{\}]*)\/\//", "<i>$1</i>", $str);
		$str = preg_replace("/__([^_]*)__/", "<u>$1</u>", $str);
		$str = preg_replace("/--([^-]*)--/", "<del>$1</del>", $str);
		$str = preg_replace("/\[\[http:\/\/([^\|\]]*)\|([^\]]*)\]\]/", '<a href="http://$1"'.$a_target.'>$2</a>', $str);
		$str = preg_replace("/\[\[http:\/\/([^\|\]]*)\]\]/", '<a href="http://$1"'.$a_target.'>$1</a>', $str);
		$str = preg_replace("/\{\{http:\/\/([^\|\}\? ]*)\}\}/", '<img src="http://$1" alt="$1" />', $str);
		$str = preg_replace("/\{\{http:\/\/([^\|\}\? ]*)\?([0-9]*)\}\}/", '<img src="http://$1" width="$2" alt="$1" />', $str);
		$str = preg_replace("/\{\{http:\/\/([^\|\}\? ]*)\?([0-9]*)x([0-9]*)\}\}/", '<img src="http://$1" width="$2" height="$3" alt="$1" />', $str);
		$str = preg_replace("/\{\{http:\/\/([^\|\}\? ]*) *\}\}/", '<img src="http://$1" alt="$1" class="img_left" align="left" />', $str);
		$str = preg_replace("/\{\{http:\/\/([^\|\}\? ]*)\?([0-9]*) *\}\}/", '<img src="http://$1" width="$2" alt="$1" class="img_left" align="left"/>', $str);
		$str = preg_replace("/\{\{http:\/\/([^\|\}\? ]*)\?([0-9]*)x([0-9]*) *\}\}/", '<img src="http://$1" width="$2" height="$3" alt="$1" class="img_left" align="left"/>', $str);
		$str = preg_replace("/\{\{ +http:\/\/([^\|\}\? ]*)\}\}/", '<img src="http://$1" alt="$1" class="img_right" align="rignt" />', $str);
		$str = preg_replace("/\{\{ +http:\/\/([^\|\}\? ]*)\?([0-9]*)\}\}/", '<img src="http://$1" width="$2" alt="$1" class="img_right" align="right" />', $str);
		$str = preg_replace("/\{\{ +http:\/\/([^\|\}\? ]*)\?([0-9]*)x([0-9]*)\}\}/", '<img src="http://$1" width="$2" height="$3" alt="$1" class="img_right" align="right" />', $str);
		$str = preg_replace("/\{\{ +http:\/\/([^\|\}\? ]*) +\}\}/", '<img src="http://$1" alt="$1" class="img_center" />', $str);
		$str = preg_replace("/\{\{ +http:\/\/([^\|\}\? ]*)\?([0-9]*) +\}\}/", '<img src="http://$1" width="$2" alt="$1" class="img_center" />', $str);
		$str = preg_replace("/\{\{ +http:\/\/([^\|\}\? ]*)\?([0-9]*)x([0-9]*) +\}\}/", '<img src="http://$1" width="$2" height="$3" alt="$1" class="img_center" />', $str);
		$str = preg_replace("/< ?\.([^\| ]*) ?\|([^>]*)>/", '<span class="$1">$2</span>', $str);
		$str = preg_replace("/< ?= ?([^\| ]*) ?\|([^>]*)>/", '<span style="$1">$2</span>', $str);
 
		return $str;
	}
 
	function cording_start($str){
		$array = preg_split("/[\r\n]/", $str, -1, PREG_SPLIT_NO_EMPTY);
		$end = count($array);
		$result = '';
 
		for ($i=0; $i<$end; $i++){
			$line = $array[$i];
			if (preg_match("/^(\={2}) ?([^=]*)$/", $line, $data)){
				$this->cording_tag_close("h2", FALSE);
				echo "<h2>".$data[2]."</h2>\n";
			}else if (preg_match("/^(\={3}) ?([^=]*)$/", $line, $data)){
				$this->cording_tag_close("h3", FALSE);
				echo "<h3>".$data[2]."</h3>\n";
			}else if (preg_match("/^(\={4}) ?([^=]*)$/", $line, $data)){
				$this->cording_tag_close("h4", FALSE);
				echo "<h3>".$data[2]."</h4>\n";
			}else if (preg_match("/^(\={5}) ?([^=]*)$/", $line, $data)){
				$this->cording_tag_close("h5", FALSE);
				echo "<h3>".$data[2]."</h5>\n";
			}else if (preg_match("/^( {2,3}-) ?(.*)$/", $line, $data)){
				echo $this->cording_list(1, 'ol', $data[2]);
			}else if (preg_match("/^( {4,5}-) ?(.*)$/", $line, $data)){
				echo $this->cording_list(2, 'ol', $data[2]);
			}else if (preg_match("/^( {6,7}-) ?(.*)$/", $line, $data)){
				echo $this->cording_list(3, 'ol', $data[2]);
			}else if (preg_match("/^( {8,9}-) ?(.*)$/", $line, $data)){
				echo $this->cording_list(4, 'ol', $data[2]);
			}else if (preg_match("/^( {2,3}\*) ?(.*)$/", $line, $data)){
				echo $this->cording_list(1, 'ul', $data[2]);
			}else if (preg_match("/^( {4,5}\*) ?(.*)$/", $line, $data)){
				echo $this->cording_list(2, 'ul', $data[2]);
			}else if (preg_match("/^( {6,7}\*) ?(.*)$/", $line, $data)){
				echo $this->cording_list(3, 'ul', $data[2]);
			}else if (preg_match("/^( {8,9}\*) ?(.*)$/", $line, $data)){
				echo $this->cording_list(4, 'ul', $data[2]);
			}else if (preg_match("/^\#([^\.\#]*) ?\.([^\.\#\{]*)\{$/", $line, $data)){
				$this->cording_tag_close("div", TRUE);
				echo '<div id="'.$data[1].'" class="'.$data[2].'">'."\n";
			}else if (preg_match("/^\.([^\.\#]*) ?\#([^\.\#\{]*)\{$/", $line, $data)){
				$this->cording_tag_close("div", TRUE);
				echo '<div class="'.$data[1].'" id="'.$data[2].'">'."\n";
			}else if (preg_match("/^\.([^\.\#\{]*)\{$/", $line, $data)){
				$this->cording_tag_close("div", TRUE);
				echo '<div class="'.$data[1].'">'."\n";
			}else if (preg_match("/^\#([^\.\#\{]*)\{$/", $line, $data)){
				$this->cording_tag_close("div", TRUE);
				echo '<div id="'.$data[1].'">'."\n";
			}else if ($line == "}" && in_array("div", $this->cording_open_tags)){
				while ($this->cording_open_tags[0] != "div"){
					echo '</'.array_shift($this->cording_open_tags).">\n";
				}
				echo '</'.array_shift($this->cording_open_tags).">\n";
			}else{
				$this->cording_tag_close("p", TRUE);
				echo "<p>\n";
				echo $this->cording_text($line)."\n";
			}
		}
		$this->cording_tag_close("", FALSE);
	}
 
}
 
?>

 file_get_contents()関数等でテキストファイルを読み込み、そのデータをそのままcording_start()メソッドに投げてやることでHTMLに変換します。

 例えば、こんな感じです。

cording_test.php
<?
// 読み込ませるファイルのパスを指定
$file = 'path/to/file';
require_once("cording.php");
 
// デバッグのため「text/plain」で吐き出します
header("Content-Type: text/plain; charset=UTF-8");
 
$obj = new cording();
$obj->cording_start(file_get_contents($file));
 
?>

 ちなみに、変換させるテキストファイルはこちらのページに書いてある文法を解読します。

ここがムズい!

 HTMLタグは、入れ子にしてどんどん開いていくことができます。例えば…

<div>
  <p>
    <ul>
      <li>aaaaa</li>
      <ul>
        <li>bbbbb</li>

 といった感じです。開いていくのは楽なんですが、次に来るタグによって「どこまで閉じるか」を判断するロジックが難しいです。

 例えば、上のHTMLコードの次に「<h2>」なんてタグを入れようと思ったら、<ul>は2つとも閉じるとして、<p>は閉じるのか? <div>は閉じるのか?? <h2>ではなく「<p>テキスト</p>」が来たらどこまで閉じるのか?? このあたりの判断がWikiエンジンをつくるにあたっての1つの山ではないかと思います。

 ボクのコード内では「cording_tag_close()」と「cording_tag_close_check()」とでタグを閉じるか閉じないか(閉じるとしたらどこまで閉じるのか)を判断させています。

次はテーブルの処理

 リスト処理が完成したのでずいぶん楽になりました。次は、最大の難関であるテーブルです。じっくり考えてみます。

Discussion