DOM弄り
仕事中息抜きに書いてた。
文書の元々のソースコードと実際の表示が違うというのをやってみたかった。
ちなみに文書を読み込んだ時点で文書型は決まってしまうので、あとからDOCTYPE書き換えても意味ないです。
https://dl.dropboxusercontent.com/u/414379/www/DOMreplace/index.html
文字コードはUTF-8(BOMなし)で作ったけど、英数だけだとバイナリレベルで見たときにASCIIと変わらない。
そうなると、DOM操作で日本語を挿入したときにWebブラウザが盛大に文字化け*1するので、それを防ぐために無理やり日本語をすべり込ませてみた。
DOCTYPEやDocumentそのものを作り出すメソッドがdocument.implementation
にあるけど、まぁ普通にJavaScriptコードを書く分には一生お世話にならないメソッドたちでしょう。
JavaやPerlなどの高位なプログラムからXMLを出力するときに使う為にあるものかなと。
CSSは特に何か意識したわけでもなく、なんとなく書いた。
var pi = document.createProcessingInstruction('xml', 'version="1.0" encoding="UTF-8"'); document.appendChild(pi); //==> <?xml version="1.0" encoding="UTF-8"?>
ってやってるけど、XML宣言はPIではないので、これは実は間違い。
比較的新しいバージョンのMozilla系エンジンでは動くけど、その他では例外を吐く。
その為try-catch
で捕まえてエラーコードだけ出力するようにしておいてある。
XML宣言は、文字コードがUTF-8の場合は省略してもいいことになっており、それ故にXHTMLでValidだと思います。
それから、createElement
で要素生成してるけど、今回のケースではXHTMLで作っていることを明示的に示す必要があると思うので、本当ならcreateElementNS
を使うべきだと思った。
めんどくさいのでしてない。
また、極力Web標準仕様に則って書いているため、古いIE(IE7以前?)では
elem.setAttribute("class", "stretchable");
で処理がうまく通らないと思う。
理由は、古いIEではsetAttribute
メソッドが、プロパティへの代入のシンタックスシュガーであるため。
つまりelem.setAttribute("class", "stretchable")
はIE内部でelem.class = "stretchable"
として解釈されてしまっていた。というような記述をどこかで読んだ。
ちなみにsetAttribute
について、一部で第二引数にオブジェクトを入れてるコードを紹介しているサイトが散見されるけど、仕様上は第一引数も第二引数もプリミティブな文字列でないとダメだったはず。
どうしてもっていう場合は「古いIE向けのハックとして」程度に収めておくべきかと。
スーパーpre記法で書いたコード部分に行番号を出すようにした
こんなコードをフッタに入れた。
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script> <script> jQuery(function($){ $("pre.code").each(function(){ var df = document.createDocumentFragment(); var ol = $('<ol>').appendTo(df); $.each($(this).html().split("\n").filter(function(e, i, arr){ return i !== arr.length - 1; }), function(i){ $('<li>').html(this).appendTo(ol); }); $(this).empty().append(df); }); }); </script>
※jQueryで書いてるけど、ES5のメソッド使ってるので結局IE8以前では動きません。
今回はやっつけ実装なので、時間があったらまた見直してみる。
追記 (2014/01/18 13:55)
IE8以下でも動作するようにした。
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script> <script> jQuery(function($){ $("pre.code").each(function(){ var df = document.createDocumentFragment(); var ol = $('<ol>').appendTo(df); $.each($(this).html().split("\n").slice(0, -1), function(i){ $('<li>').html(this).appendTo(ol); }); $(this).empty().append(df); }); }); </script>
たぶん動作速度も上がったと思う。体感はできないけど。
どこが変わったかっつーとArray.prototype.filter
をやめてArray.prototype.slice
にしただけ。
要は配列の最後の要素が邪魔だっただけなので、無理にfilter使う必要が全くなかった。
追記 2020/07/02
純粋な JavaScript のみで再実装しました。jQueryからの脱却。
処理内容自体は同じです。
<script> document.addEventListener('DOMContentLoaded', () => { document.getElementById('main-inner').querySelectorAll('pre.code').forEach(el => { const ol = document.createElement('ol'); ol.append(...el.innerHTML.split('\n').slice(0, -1) .map(s => Object.assign(document.createElement('li'), { innerHTML: s }))); el.innerHTML = ''; el.append(ol); }); }); </script>
IEの開発者コンソールで見るテキストノードについて
IEの開発者コンソールで
var textNodeIterator = document.createNodeIterator( document, NodeFilter.SHOW_TEXT, function(node){ return /^\s*$/.test(node.textContent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; }, false); textNodeIterator.nextNode();
ってやるとコンソールにEmptyTextNode
って表示される。
あー空のテキストノードに特別に名前つけてんだーって思いました。っていうそれだけのお話。
それだけだと寂しいので少しだけ解説。
Document#createNodeIterator
でNodeIterator
っつーオブジェクトが作られる。
これはDOM Level 2で定義されたインターフェースで、ドキュメント全体から条件に一致したDOMノードを自由に取ってこれる便利なアレ。
イテレータなので、nextNode()
previousNode()
で内部のポインタが動き、逐一ポインタが指すノードを返す。
ポインタが終端・または始端にある状態でそれぞれnextNode()
previousNode()
を実行するとnull
を返す。
このインターフェースには現在のポインタに位置するノードを返すメソッド・プロパティはないので、
var currentNode;
currentNode = textNodeIterator.nextNode();
などとしないと値を保持できない。
ちなみにNodeIterator
には上記2つ以外にdetach()
を加えた3つしかメソッドが存在しない。
以下、引数について書く。
第一引数がルートノード。
ここに指定したノード、およびその子孫から条件に一致したものを抜き取る。
第二引数がどの属性のノードを持ってくるか。
ここでまた新しくNodeFilter
インターフェースが出てきたけど、ここにNodeIterator
とTreeWalker
*1で使われる定数が保持されてる。
今回はテキストノードのみが欲しかったのでNodeFilter.SHOW_TEXT
とした。
第三引数には、第一引数と第二引数の条件にマッチしたノードを引数としたコールバック関数を置く。*2
この関数の戻り値としてNodeFilter.FILTER_ACCEPT
を受け取るとそのノードはパスされ、逆にNodeFilter.FILTER_SKIP
を受け取るとそのノードは弾かれる。
他にNodeFilter.FILTER_REJECT
というのもあるが、これはNodeIterator
を使っている場合、NodeFilter.FILTER_SKIP
と同じ意味を持つ。
違いが出てくるのはTreeWalker
の場合だけど、ここでは割愛。
ちなみに、フィルタリングなんか必要ねぇよっていう場合は、第三引数はnull
でいいです。
第四引数は、エンティティ参照を文字列で置き換えるかの真偽値。だと思う。
true
にすると、例えば「♠」が「♠」に置き換えられる。たぶん。
実は試したことない。
勝手に置き換わると後々面倒だし、無難にfalse
にしとくのがいいと思う。
正規表現部分については今更説明必要ないと思うけど、一応。
/^\s*$/
の意味するところは、最初から最後まで半角スペースやタブ文字、改行コードのみになっていればマッチ。
プラスではなくアスタリスクなので0回以上の出現、つまり空文字の場合もマッチする。
これらを総合して、冒頭のコードの意味を見ると、
ドキュメント全体から、半角スペース・タブ文字・改行文字のみで構成された文字列、あるいは空文字のテキストノードをイテレータとして抽出し、イテレータの最初の1つを取り出した。
と言った感じ。
以上。
少しだけ解説のつもりが、記事の大半が解説になってしまった。
Ancia向けのCSSエディタ書きました
まだ中途半端な実装だし暇があればまだ改良するつもりだけど、ひとまず公開。
ここに置いておきます。
UserExtension for Ancia
以下のスクリプトは軽量Webブラウザ「Ancia(アンシア)」向けのものです。
公式サイト: 名称未決定 - FrontPage
特定のURLで自動的に発動するタイプ
- nicosearch.js2013/12/04
- niconicoの検索窓にサジェスト機能を付与します。
- visibleExtIcon.js2013/12/07
- リンクのURLの拡張子がスクリプトに内蔵された一覧に合致すると、そのカーソルの近くに拡張子アイコンを表示します。
- searchWiki.js2013/12/09
- サイト内検索を拡張します。
文字列を選択すると「検索」ボタンがマウスカーソルの近くに表示されます。
- サイト内検索を拡張します。
能動的に呼び出すタイプ
- userStylesheetEditor.js2013/12/19
- Ancia向けUserStyleSheetの編集に特化したエディターを提供します。
スクリプトについて
これらの一部、または全部は公式サイトでも配布されています。
ただし公式サイトのものはバージョンが古い場合があります。