フロントサイドエンジニアという選択肢

HTMLコーダー → ECサイト運営 → システムエンジニア という経歴の著者がフロントサイトエンジニアという職業に今後の活路を見出し、その道に進むために取得した技術を貯めておくブログ

ソースコードに自動でhtmlタグ(とCSSクラス)を割り当てるスクリプトを作ってみた

最近はどのブログも、ソースコードを表示する際には行番号付きのブロックで、単語を色分けした、綺麗で見やすい表示になっています。ちょっと調べてみると、専用のライブラリやHtml用に変換してくれるフリーソフトなんかもあるようです。

ただ、折角Javascriptを勉強しているわけなのだから、調度良いので自分で作ってみることにしました。

2015年9月12日追記:
 ソースコードに自動でhtmlタグ(とCSSクラス)を割り当てるスクリプト その2 という記事で新しいスクリプトを公開しました。

組み込みたい機能は以下のとおり。

  • 表示スペースの左側に行番号を入れたい
  • 一行置きに背景色が変わるようにしたい
  • 変数名、関数名、ifやforなどおなじみの機能を色分けしたい
  • preタグにソースを記述するだけで、上記を全部自動で行うようにしたい
  • 適切なタグでマークアップしたい

最後の項目に関しては現在、ソースコードにスタイルを当てるときはリストタグを使うというのが主流になっているようなので、もっと適切な方法が無いか試したいという気持ちがありました。
結果として、liをcodeタグに置き換えるという方法に落ち着きました。一行毎にcodeタグが入るわけですが、インライン属性ですし、codeタグ内に記述するという部分は正しいので、その方法を採用しました。

完成したJavascriptコードとスタイルシートは以下のようになっています。(当然、ソースコードは作成したJavascriptで表示しています。

Javascript
// preタグを全て取得してスタイルを適用させる
var codeCollection = document.getElementsByTagName("pre");
for(var nodeIndex in codeCollection)
{
    setHtml(codeCollection[nodeIndex]);
}

// テキストを書き換えてhtmlタグを追加する
function setHtml(codeNode)
{
    if(codeNode.innerHTML)
    {
        var innerText = codeNode.innerHTML;

        // ソースに使用しているHtmlタグを表示できるように修正
        innerText = innerText.replace(/</g,'<span class="htmlTag">&lt;').replace(/>/g,'&gt;</span>');
        
        // 変数にスタイルを適用
        innerText = setVariableCss(innerText);
        
        // 関数名にスタイルを適用
        innerText = setFunctionNameCss(innerText);
        
        // 以下の処理は一行毎に処理したほうが楽なので、1行毎に処理
        var lines = innerText.split(/\n/);
        for(var i in lines)
        {
            // 行数や行のスタイルを適用
            lines[i] = createLine(i-0+1, lines[i]);
            
            // コメントにスタイルを適用
            lines[i] = setCommentCss(lines[i]);
            
            // 予約語にスタイルを適用
            lines[i] = setWordsCss(lines[i]);
        }
        codeNode.innerHTML = lines.join('');
    }
}

// コードの一行を作成する
function createLine(lineNum, text)
{
    var lineNumber = '<span class="lines">' + lineNum + '</span>';
    return '<code class="jsCode">' + lineNumber + text + '</code>';
}

// コメントにCSSを適用させる(回避させる場合は行末に「@@」と入れる)
function setCommentCss(text)
{
    if(text.search(/[^http:]\/\//) >= 0)@@
    {
        // 行末に@@の文字が入っている場合はコメントとしない
        var rowText = text.replace('</code>','').replace('<br />','').trim();
        if(rowText.indexOf('@@')+2 == rowText.length)
        {
            return text.replace(/@@/, '');
        }
        else
        {
            return text.replace(/(\/\/.+)(?=<\/code>)/, '<span class="comment">$1</span>');
        }
    }
    return text;
}

// varで定義された変数の色を変える
function setVariableCss(text)
{
    // テキスト内の全ての変数を取得
    var varRegs = /(var\s)(\w+)([\s=])/g;
    var result = text.match(varRegs);
    
    // 変数ごとに処理
    for(var v in result)
    {
        var varText = result[v].replace('var ','').replace(/\W/,'');
        
        // 判定する文字タイプ
        var regArray = [
            ['[^\\w\\[\\(\\"=>](' + varText + ')[^\\w\\[\\]\\.//)=]',' ',' '],@@
            ['[^\\w\\[\\(\\"=>](' + varText + ')\\[',' ','\['],
            ['[^\\w\\[\\(\\"=>](' + varText + ')\\.',' ','.'],
            ['[^\\w\\[\\(\\"=>](' + varText + ')\\=',' ','='],
            ['[^\\w\\[\\(\\"=>](' + varText + ')\\)',' ',')'],
            ['=(' + varText + ')[^\\w\\[\\.//)=]','=',' '],@@
            ['=(' + varText + ')\\[','=','\['],
            ['=(' + varText + ')\\.','=','.'],
            ['\\[(' + varText + ')[^\\w\\[\\.//)=]','\[','\]'],@@
            ['\\[(' + varText + ')\\]','\[','\]'],
            ['\\[(' + varText + ')\\.','\[','.'],
            ['\\((' + varText + ')\\[','\(','\['],
            ['\\((' + varText + ')\\,','\(','\,'],
            ['\\((' + varText + ')\\)','\(','\)']
        ]
        
        // タグを組み込んだテキストと差し替え
        for(var r in regArray)
        {
            var innerReg = new RegExp(regArray[r][0], 'g');
            text = text.replace(innerReg, regArray[r][1]+'<var>$1</var>'+ regArray[r][2]);
        }
    }
    return text;
}

// 関数名にスタイルを適用させる
function setFunctionNameCss(text)
{
    var funcRegs = /(function\s)(\w+)\W/g;
    var results = text.match(funcRegs);
    for(var f in results)
    {
        // 変数を取得
        var functionNameText = results[f].replace('function ','').replace(/\(/,'');
        
        // 差し替え用のテキストを作成
        var functionTextExtra = results[f].split(functionNameText);
        var functionRepText = ' <span style="color:green;">' + functionNameText + '</span>(';
        
        // タグを組み込んだテキストと差し替え
        text = text.replace(new RegExp(' ' + functionNameText + '\\(', 'g'), functionRepText);
        
    }
    return text;
}

// 予約語にスタイルを適用させる
function setWordsCss(text)
{
    // スタイルを適用させる予約語
    var reservedWords = [
        'function ',
        'var ',
        'for',
        'if',
        'while',
        ' in ',
        'return ',
        'break ',
        'continue ',
        'new ',
        'switch',
        'case'
        ];
    
    for(var t in reservedWords)
    {
        var word = reservedWords[t];
        text = text.replace(word, '<span class="reservedWord">' + word + '</span>'); 
    }
    
    return text;
}
スタイルシート(色分けは対応していません)
pre {
    line-height: 15px;
    border: solid 1px #999999;
    overflow: auto;
    padding: 0;
}

pre>code.jsCode {
    display: block;
    font-size: 12px;
    letter-spacing: 1px;
    font-family: Verdana;
    overflow-y: hidden;
    border-radius: 0;
}

pre>code:nth-child(2n){
    background-color: #f4f4f6;
}

pre>code>.lines{
    display: inline-block;
    background-color: black;
    color: white;
    width: 3em;
    text-align: center;
    padding: 0;
    margin-right: 15px;
}

pre>code> .comment{
    font-size: 13px;
    color: #99a;
}

pre>code> .reservedWord{
    color: #66f;
}

pre>code> .htmlTag{
    color:orange;
}

pre>code>var{
    color:red;
}
    
pre>code> .functionName{
    color:#070;
}

jsファイルを見たい方は、下のリンクからダウンロード出来ます。変数の色分けや正規表現を対象から除外するなど、複雑な部分もあったのでまだバグが残っている可能性があります。自由にご利用いただいて結構ですが、表示の不具合などは自己責任でお願い致します。
JSファイルダウンロード先set_code_style.js
使い方としては、ページの最後で上記のファイルを読み込んで、ソースコードをpreタグの中に記述するだけです。jsファイルの読込が完了すると勝手にpreタグを取得してHtmlタグを組み込む処理を開始します。上記のCSSを使用すると、このページと同じように表示されるようになります。

参考として確認済みのバグと対応方法を記述しておきます。

  • 正規表現で「//」とすると、コメントと認識してコメント用のスタイルを充ててしまう。対応策として適用したくない部分には行末に「@@」と記述すると回避できるように修正。
  • Htmlタグはそのままタグとして扱われます。画面に表示されるようにする場合は&gt;、&lt;を使用するか、または<を<、>を>と日本語で記述することでスタイルが適用されるようになります(表示の際は<が<に、>が>に変換される仕様になってます)。
  • 一つのvarで2つ以上の関数を一度に定義するとスタイルが適用されません。その他にも記述方法によってスタイル(html)が適用されない場合があるので、その場合は手動でhtmlを記述して下さい。