LineNumberWriterを作るに当たって気づいた点やその他補足など。自分で書いていて改めて気付きましたけど、かなりの欠陥品です…orz
基本設計
行番号を振るための処理は、基本的にsyntaxhighlithter.jsとほとんど同じ。というか、syntaxhighlighter.jsを参考にして作りました。
処理の流れとしては、
- まずページ内に<pre><code>とマークアップされている部分を取得します。
それら個々を<ol><li>番号順リストのマークアップに変換します。それら個々を<ol><li>番号順リストのマークアップに変換したものと、そのままのもの(プレーンテキスト)、それぞれ二つづつを生成します。プレーンテキストの方はデフォルトで非表示にします- 奇数行と偶数行のli要素それぞれに別のclass属性値を設定します。
- 変換後のコードとプレーンテキストとを切り替えるヘッダー部分を生成します。
最後にdiv要素で包みます(後でスタイルを整える都合)。最後にこれらをdiv要素内に書き出し、元の<pre><code>部分と差し替えます。
<pre><code>function sample() {
alert("sample");
}</code></pre>
これが
<div class="LNW">
<div class="header" style="display:none">
<a href="#" class="ctrl1">with line number</a>
<span class="ctrl2">plain text</span>
</div>
<div class="header">
<span class="ctrl1">with line number</span>
<a href="#" class="ctrl2">plain text</a>
</div>
<ol>
<li class="odd">function sample() {</li>
<li class="even"> alert("sample");</li>
<li class="odd">}</li>
</ol>
<pre style="display:none"><code>function sample() {
alert("sample");
}</code></pre>
</div>
こうなります。
改行をどうやって拾うか
取得したDOM要素のinnerHTMLプロパティから拾えれば全く問題なかったんです。innerHTMLから改行を拾う処理はこんな感じ。
var str = codeElement.innerHTML;
str = str.replace(/\r?\n/g, "\r");
で、splitメソッドで分割して、li要素に突っ込んでいけばOKと思っていました。
var line = str.split("\r");
var i,l,li;
for(i=0,l=line.length; i<l; i++) {
li = document.createElement("li");
li.innerHTML = line[i];
}
ところが、、WinIEがどうやっても改行を拾ってくれません。innerHTMLにした瞬間に全部改行が消えてしまう・・・!
そこで、取得した要素のコード部分をテキストノードとして拾い、そのnodeValueから拾ったら、うまくいきました。ただしこのやり方だと、テキストノード以外のノード(要素ノードとか)が混じっていると処理が複雑になりすぎてしまう・・・。ここで壁にぶち当たります。
「今の自分にはこりゃ作れない」という気になってきたので気分を切り替え、そういう仕様と割り切っていっそのことタグを使用不可にすればいいじゃんと考えました。取得したDOM要素内には常にテキストノードだけが入っている状態なら良いわけですから。
Safariのヘンなところ発見
コードの一行目に含まれているのが改行のみの場合、Safariがその改行を拾ってくれません。一行目が改行だけってシチュエーションがまずないので、これもスルーしています。
次のようになるべきコードが
<ol>
<li class="odd"></li>
<li class="even">function sample() {</li>
<li class="odd"> alert("sample");</li>
<li class="even">}</li>
</ol>
Safariでは次のようになります。
<ol>
<li class="odd">function sample() {</li>
<li class="even"> alert("sample");</li>
<li class="odd">}</li>
</ol>
ブラウザ振り分け処理
WinIEだけ別処理にしたいので振り分けます。確かWinIEだけ実装している、document.allオブジェクトを利用してみました。が、Operaもひっかかってきた(Operaもdocument.allを実装していた)ので、Operaだけが実装しているwindow.operaオブジェクトをさらに利用します。
if(document.all && !window.opera) {
WinIE用の処理
} else {
その他のブラウザ用の処理
}
ホントはもっとスマートなやり方があると思います・・・。
半角スペースの変換問題
いっぽう、WinIE以外のブラウザではinnerHTMLから改行が拾えるからタグ使えるんじゃね?と思っていたら、別の問題にぶち当たりました。次に問題となったのは半角スペースです。
まず前提として、コードのインデントなどに使われる半角スペースを拾い、 へと変換する処理を行っています。タグに属性値をセットしている場合、この処理で属性名前の半角スペースを拾われてしまい、ヘンテコな状態に変換されてしまいました。
sample text
<a href="sample.html">sample text</a>
sample text
これが
sample text
<a href="sample.html">sample text</a>
sample text
こうなっちゃいました。
というわけで、結局タグは使えない仕様で行くことに決定。
CSSでも…
CSSを書く段階になってもWinIEに苦しめられます。
横に長いコードを詰め込んだときにスクロールバーを出すためにoverflow:autoを設定するわけですが、スクロールバーが出た状態のWinIEがとてもヘン。それから、ol要素にoverflow設定をするとIE6で幅がヘンなことに。syntaxhighlighter.jsを参考に、全体をdiv要素で包み、そちらにoverflow設定をするこにしました。
また、横スクロールバーが出た際にWinIEは、スクロールバーの高さ分をそのボックスの高さに含めてしまう模様。そしてそのせいで、横スクロールバーが出ると同時に、縦スクロールバーも出てしまう・・・。
横長なコードのくせに1行か2行しかない場合が最悪で、コードが横スクロールバーに隠れてしまい、ロクに見ることができなくなってしまいます。これはお話にならない!というわけでWinIEだけハックで下paddingを設定し、スクロールバー分の高さを稼ぎました。これによって縦スクロールバーが出なくなり、コードの閲覧性は上がりました。が、スクロールバーが出た時に下marginが狭くなるという副作用が・・・。もうここでお手上げとなりました。
CSSが書ける方であれば、linenumberwriter.cssのソースコードをいじれば、変換後のスタイルを自由に変更できます。お気に入りのスタイルにどうぞ。
div.LNWの上下のmargin、color、font-size辺り、それから各要素のbackground-colorやborder辺りがいじりどころだと思います。0.2にバージョンアップするに当たり、CSSも大分見直しました。