問題的提出是我需要將另一個網頁的某些內容解析出來並集成到當前正在處理的網頁中。第一想法就是,使用dojo.xhrGet得到網頁的內容,再使用某種Javascript Library來解析。我熟悉的dojo並沒有這種庫,查詢瞭一下,jQuery也沒有。後來發現jQuery的作者,鼎鼎大名的John Resig有這樣一個庫,支持SAX語法和DOM樹的構建:
Pure JavaScript HTML Parser
拿我要試驗的網頁試瞭一下,很可惜,它crash掉瞭(這個網頁有好幾十K大小,也不一定很規范)。不僅想念起Java來,在Java下我們的兵器庫裡會有一堆這樣的東西,有不少甚至支持cssrendeing和Javascript執行。
怎麼辦?我的需求並不復雜,也許正則表達式就可以用瞭,但是想起復雜的css查詢,以及要兼容網頁未來可能的變化,所以這一方法並不值得去試。
突然想起來,browser就是最完美的HTML解析器,如果讓browser新開一個窗口加載這個網頁,然後再使用DOM API來分析網頁,這樣要獲取我所需要的內容一定會更加準確、靈活,將來網頁改變後,程序的修改也顯然會更容易。當然,新開窗口的做法不太優雅(甚至會引起用戶的錯愕),我們使用frame來實現。
Frame共有兩類,分別由frameset,frame標簽或iframe標簽創建。如果一個HTML文件中含有frameset,則該文件隻應含有<head>,<frameset>和<frame>標簽,不應該有<body>標簽。而iframe則用來創建一個內聯frame,它包含在<body>標簽內。
很多教科書講關於<frameset>創建的frame的內容,以及各個frame之間window和document的關系,比如《Javascript高級程序設計》(Nicholas Zakas)。而通過iframe創建的frame有以下關系:
原始文檔對應的窗口對象,以下簡稱iWindow,它擁有下列對象:
iWindow.frames: 擁有的子frame數組。可以用frames.length == 0判斷一個窗口是否有frame。
iWindow == top
iWindow == iWindow.parent
被創建的frame的文檔對象,根據DOM規范,由frameNode.contentDocument來引用,其窗口對象應為frameNode.contentWindow,但註意各瀏覽器的實現不盡相同。這也正是dojo可以幫忙的地方。下面來看看dojo的實現:
創建iframe www.aiwalls.com
dojo.io.iframe.create(/*String*/fname, /*String*/onloadStr, /*String?*/uri)
第一個參數是你將要為該frame指定的名字。實際上dojo同時也將該名字當成該iframe的id屬性使用。因此,你不可以多次使用同一名字來創建iframe。第二個參數指定瞭當該iframe內容加載完畢時應該觸發的事件,它可以是一小段javascript程序。第三個參數可選,如果不指定,則其會加載dojo.js所在域的某個指定位置下的一個空白html文件。如果你是跨域加載dojo的話(在CDN使用越來越頻繁的情況下,跨域加載變得更加常見),這會引起一個安全錯誤。也許可以將這個參數指定為“about:blank’,firefox和ie都支持這個語法。
操縱iframe文檔對象
應該始終使用dojo提供的方法來獲取對iframe文檔對象的引用,這是因為各個瀏覽器對象層次結構不同。
dojo.io.iframe.doc(/*DOMNode*/myFrame);
這裡的myFrame是先前通過create函數創建的一個DOMNode。記住inline frame就是當前文檔對象中的一個結點。如果有興趣看一下dojo的源代碼,它對不同瀏覽器是這樣實現對這個文檔的選擇的:
doc: function(/*DOMNode*/iframeNode){
//summary: Returns the document object associated with the iframe DOM Node argument.
var doc = iframeNode.contentDocument || // W3
(
(
(iframeNode.name) && (iframeNode.document) &&
(dojo.doc.getElementsByTagName("iframe")[iframeNode.name].contentWindow) &&
(dojo.doc.getElementsByTagName("iframe")[iframeNode.name].contentWindow.document)
)
) || // IE
(
(iframeNode.name)&&(dojo.doc.frames[iframeNode.name])&&
(dojo.doc.frames[iframeNode.name].document)
) || null;
return doc;
},
對我們拿到的這個doc對象,如何運用DOM API以及dojo的css選擇語法呢?
jQuery的核心語法是$,通過$選擇瞭一個結點時,其默認根結點是當前文檔對象,但是可以通過$(,)的第二個參數來選擇其根結點,比如:
$('h1', window.parent.frames[0].document).remove()
應該說在這一點上,jQuery的語法相當簡潔。在dojo中,我們需要這樣做:
dojo.withDoc(/*the doc object of iframe*/iframeDoc, method, scope, args);
其中scope是dojo用來查找第二個參數method以及在method中引用到的一切變量的環境。如果method是’byId’,則scope應為dojo。args是調用method時需要傳入的參數。
iframe的銷毀
dojo的文檔沒有提及如何銷毀一個iframe,在它自身的實現中甚至沒有實現這個功能。考慮到iframe隻不過是當前document的一個結點,所以應該可以用DOM API來銷毀一個iframe。這一點未及實驗。
替換目標鏈接
比起destroy操作,可能更常見的操作是通過iframe來分別加載不同的html文檔。可以通過dojo.io.iframe.setSrc來實現:
dojo.io.iframe.setSrc(/*DOMNode*/iframe, /*String*/src, /*Boolean*/replace);
第三個參數值得註意,根據實驗,如是指定為false,在firefox中可能導致iframe並不更新文檔。
關於這個函數,有一些行為值得進一步研究。我們在創建iframe時指定瞭onload事件的處理代碼,是因為除瞭回調之外,並無好的辦法來確保處理程序一定在iframe文檔加載完成之後再執行。那麼,當setSrc為iframe替換瞭新的location之後,這個onload事件怎麼辦?經過在firefox中試驗後,令人驚訝的是,先前創建iframe時指定的onload事件處理程序仍然有效—而dojo並未為此做些什麼。這說明,dojo創建iframe時指定的onload事件是window的事件,而非document,甚至更底層一點,body的事件。
結論
在Javascript的世界裡,有時候程序員是在跳戴著鐐銬的舞蹈,這時候藝術比科學更重要。通過使用iframe,比較有效地解決瞭文檔的解析問題—程序員可以仍然以他們熟悉的API和方式去操作這些文檔,這比起費心費力去尋找一個不太成熟的庫,甚至自己去實現某些功能要好得多。
iframe方法仍然有著自己的缺點—在某些場合也許是致命的。瀏覽器越來越多地限制跨域操作,因此如果在iframe裡加載瞭另一個域的HTML文檔,你將隻可以訪問title,location等等屬性,而無法訪問其內容。由於這是瀏覽器的限制,因此無論是dojo還是jQuery都註定無法饒過去。一個惟一的辦法,也許是通過link或者<script>標簽來載入跨域內容—這又使得基於Javascript的HTML解析器仍然有存在的必要。
摘自:盛夏蓮花- 前沿web技術