Web/PHP

구문강조

aucd29 2013. 9. 26. 21:48
<?php
class Syntax
{
    var $rule;
    var $curr_rule;
    var $color;
    var $prefix;

    function syntax($rule = null)
    {
        $this->rule = array();
        $this->prefix = false;
        if ($rule) $this->setRule($rule);
    }

    function setRule($rule)
    {
        static $dir = null;
        
        $r = array();
        $curr_ = null;

        if (array_key_exists($rule, $this->rule)) {
            $this->curr_rule = $rule;
            return true;
        }

        if ($dir == null) {
            $dir = dirname(__FILE__);
            $slash = strpos($dir, '\\')>0?'\\':'/';
            $dir .= $slash.'syntax'.$slash;
        }

        if (!file_exists($dir.$rule.'.stx')) return false;

        $fp = fopen($dir.$rule.'.stx', 'r');
        while (!feof($fp))
        {
            $line = trim(fgets($fp, 128));
            if (strlen($line) == 0 || $line{0} == ';' || $line{0} == ' ') continue;
            else if ($line{0} == '#')
            {
                list($key, $val) = explode('=', $line, 2);
                $key = strtoupper(substr($key, 1));
                $val = trim($val);

                if (empty($val)) continue;
                
                switch ($key)
                {
                    case 'DELIMITER':
                        $len = strlen($val);
                        for ($i = 0; $i < $len; $i++) $r['delimeter'][] = $val{$i};
                        break;
                    case 'QUOTATION':
                    case 'QUOTATION1':
                    case 'QUOTATION2':
                        $r['quote'][] = $val;
                        break;
                    case 'LINECOMMENT':
                    case 'LINECOMMENT1':
                    case 'LINECOMMENT2':
                        $r['linecomment'][] = $val;
                        for ($i = 0; $i < strlen($val); $i++) $r['delimeter'][] = $val{$i};
                        break;
                    case 'CONTINUE_QUOTE':
                        $r['continue_quote'] = ($val{0} == 'y' || $val{0} == 'Y')?true:false;
                        break;
                    case 'COMMENTON': $r['commenton'][0] = $val; break;
                    case 'COMMENTOFF': $r['commentoff'][0] = $val; break;
                    case 'COMMENTON1': $r['commenton'][1] = $val; break;
                    case 'COMMENTOFF1': $r['commentoff'][1] = $val; break;
                    case 'ESCAPE':
                        $r['escape'] = $val;
                        break;
                    case 'CASE':
                        $r['case'] = ($val{0} == 'y' || $val{0} == 'Y')?true:false;
                        break;
                    case 'PREFIX':
                    case 'PREFIX1':
                    case 'PREFIX2':
                    case 'PREFIX3':
                    case 'PREFIX4':
                    case 'PREFIX5':
                        $r['prefix'][] = $val;
                        break;
                    case 'SUFFIX':
                    case 'SUFFIX1':
                    case 'SUFFIX2':
                    case 'SUFFIX3':
                    case 'SUFFIX4':
                    case 'SUFFIX5':
                        $r['suffix'][] = $val;
                        break;
                    case 'EMBED_HTML':
                        $r['special_stx'] = 'html';
                        break;
                    case 'SPECIAL_STX':
                        $r['special_stx'] = $val;
                        break;
                    case 'KEYWORD':
                        if (!is_array($r['keyword'])) $r['keyword'] = array();
                        $curr_ = count($r['keyword']);
                        $r['keyword'][$curr_] = array();
                        break;
                }
            }
            else if (is_int($curr_))
            {
                $r['keyword'][$curr_][] = $r['case']?$line:strtolower($line);
            }
        }
        fclose($fp);

        if (!is_array($r['linecomment'])) $r['linecomment'] = array();
        if (!is_array($r['commenton'])) $r['commenton'] = array();
        if (!is_array($r['commentoff'])) $r['commentoff'] = array();
        $r['delimeter'] = array_unique($r['delimeter']);

        $this->rule[$rule] = $r;
        $this->curr_rule = $rule;
        unset($r);

        return true;
    }

    function on($str)
    {
        $rule = &$this->rule[$this->curr_rule];
        $str = preg_replace("/\n\r|\r\n|\r/", "\n", $str)."\n";
        $ret = '<code class="'.$this->curr_rule.'">';
        $delimeter = '';
        $word = '';
        $last_pos = 0;

        $max = strlen($str);
        $space = array("\n", "\t", " ");

        for ($i=0; $i < $max; $i++)
        {
            $c = $str{$i};

            $is_space = in_array($c, $space);

            if ($is_space)
            {
                if (strlen($delimeter) > 0) {
                    if (count($this->_pr_comment($delimeter)) > 0) $c = '';
                    $ret .= $this->_pr_delimeter(&$str, &$i, $delimeter);
                    $delimeter = '';
                } else if (strlen($word) > 0) {
                    $ret .= $this->_pr_word($word);
                    $word = '';
                }

                $ret .= $c;
            }
            else if (is_array($rule['prefix']) && in_array($c, $rule['prefix']))
            {
                $this->prefix = true;
                $ret .= '<B>'.$c.'</B>';
            }
            else if (in_array($c, $rule['quote']))
            {
                if (strlen($delimeter) > 0) {
                    $ret .= $this->_pr_delimeter(&$str, &$i, $delimeter);
                    $delimeter = '';
                } else if (strlen($word) > 0) {
                    $ret .= $this->_pr_word($word);
                    $word = '';
                }

                $ret .= $this->_pr_quote(&$str, &$i, $c);
            }
            else if (in_array($c, $rule['delimeter']))
            {
                $delimeter .= $c;
                if (strlen($word) > 0) {
                    $ret .= $this->_pr_word($word);
                    $word = '';
                }
            }
            else
            {
                if (strlen($delimeter) > 0) {
                    if (count($this->_pr_comment($delimeter)) > 0) $c = '';
                    $ret .= $this->_pr_delimeter(&$str, &$i, &$delimeter);
                    $delimeter = '';
                }
                $word .= $c;
            }
        }

        $ret = rtrim($ret).'</code>';

        return nl2br(strtr($ret, array(' '=>'  ', "\t"=>'    ')));
    }

    function onFile($filename)
    {
        if (!file_exists($filename)) return false;
        if (function_exists('file_get_contents')) return $this->on(file_get_contents($filename));
        else
        {
            $fp = fopen($filename, 'r');
            $str = fread($fp, filesize($filename));
            fclose($fp);

            return $this->on($str);
        }
    }

    function _pr_delimeter(&$str, &$offset, $delimeter)
    {
        $ret = '';
        $this->prefix = false;
        
        $compare = $this->_pr_comment($delimeter);

        if (count($compare))
        {
            $compare = array_flip($compare); ksort($compare);
            $compare = array_flip($compare); reset($compare);

            $key = key($compare);
            $val = current($compare);

            if ($val > 0) {
                $ret .= '<A>'.htmlspecialchars(substr($delimeter, 0, $val)).'</A>';
            }

            if ($key >= 1000) // 블럭 코멘트일때
            {
                $off = $this->rule[$this->curr_rule]['commentoff'][$key-1000];
                $pos = strpos($str, $off, $offset);
                $ret .= '<EM>'.htmlspecialchars(substr($delimeter, $val).substr($str, $offset, $pos - $offset + strlen($off))).'</EM>';
                $offset = $pos + strlen($off) - 1;
            }
            else // 라인 코멘트일때
            {
                $pos = strpos($str, "\n", $offset);
                $ret .= '<EM>'.htmlspecialchars(substr($delimeter, $val).substr($str, $offset, $pos - $offset)).'</EM>';
                $offset = $pos - 1;
            }
        }
        else
        {
            $ret = '<A>'.htmlspecialchars($delimeter).'</A>';
        }

        return $ret;
    }

    function _pr_quote(&$str, &$offset, $quote)
    {
        $count = 0;
        $len = strlen($str);
        $this->prefix = false;

        for ($i = $offset+1; $i < $len; $i++)
        {
            if ($str{$i} == $this->rule[$this->curr_rule]['escape'])
            {
                if ($str{$i+1} == $this->rule[$this->curr_rule]['escape'] || $str{$i+1} == $quote) { $i++; continue; }
            }
            else if ($str{$i} == $quote)
            {
                $ret = substr($str, $offset, $i - $offset + 1);
                $offset = $i;
                break;
            }
        }
        
        return '<Q>'.htmlspecialchars($ret).'</Q>';
    }

    function _pr_word($word)
    {
        $max = count($this->rule[$this->curr_rule]['keyword']);

        if ($this->prefix) {
            $this->prefix = false;
            return '<SUB>'.htmlspecialchars($word).'</SUB>';
        }

        for ($i = 0; $i < $max; $i++)
        {
            $word_temp = $this->rule[$this->curr_rule]['case']?$word:strtolower($word);
            if (in_array($word_temp, $this->rule[$this->curr_rule]['keyword'][$i]))
            {
                $ret = '<S class="'.($i+1).'">'.htmlspecialchars($word).'</S>';
                return $ret;
            }
        }

        return htmlspecialchars($word);
    }

    function _pr_comment($delimeter)
    {
        $compare = array();
        
        foreach ($this->rule[$this->curr_rule]['linecomment'] as $key=>$val)
        {
            if ( is_int($tmp = strpos($delimeter, $val)) ) $compare[$key] = $tmp;
        }

        foreach ($this->rule[$this->curr_rule]['commenton'] as $key=>$val)
        {
            if ( is_int($tmp = strpos($delimeter, $val)) ) $compare[$key+1000] = $tmp;
        }

        return $compare;
    }
}

/*
$syntax = new Syntax('js');
echo $syntax->on($str);
*/
$_POST['str'] = stripslashes($_POST['str']);
$syntax = new Syntax($_POST['type']);
echo $syntax->on($_POST['str']);
?>
<html>
<body>
<br>
구문강조 테스트<br>
<style>
/* 전체 스타일 */
CODE, CODE * {
    color:black;
    font-style:normal;
    font-weight:normal;
    text-decoration:none;
    font-size:8pt;
    font-family:돋움체;
}

CODE.html A {color:blue} /* 구분자 스타일 */
CODE.html B {color:green} /* Prefix 스타일 */
CODE.html SUB {color:green} /* Prefix 붙은 문자의 스타일 */
CODE.html EM {color:green} /* 코멘트 */
CODE.html Q {color:#000080} /* 따옴표로 묶은 거 */
CODE.html S.1 {color:blue;} /* 키워드 1 */
CODE.html S.2 {color:red;} /* 키워드 2 */
CODE.html S.3 {color:green;} /* 키워드 3 */

CODE.js A {color:red} /* 구분자 스타일 */
CODE.js EM {color:#FFCC00} /* 코멘트 */
CODE.js Q {color:#606060} /* 따옴표로 묶은 거 */
CODE.js S.1 {color:blue;} /* 키워드 1 */
CODE.js S.2 {color:red;} /* 키워드 2 */
CODE.js S.3 {color:green;} /* 키워드 3 */
CODE.js S.4 {color:#999900;} /* 키워드 4 */
CODE.js S.5 {color:#800000;} /* 키워드 5 */

CODE.css Q {color:#FF00FF} /* 따옴표로 묶은 거 */
CODE.css EM {color:green} /* 코멘트 */
CODE.css S.1 {color:blue} /* 키워드 1 */
CODE.css S.2 {color:red} /* 키워드 2 */
</style>
<form method="post">
<select name="type">
<option value="js">자바스크립트</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="python">파이선기본</option>
<option value="cpp">C++</option>
<option value="c">C</option>
</select><br>
<textarea name="str" rows="10" cols="80"><?=htmlspecialchars($_POST['str'])?></textarea><br>
<input type="submit" value="전송">
</form>
</body>
</html>