上個月
Java 技術講師 Sam Pullara 向我演示了他最新的支持 Java 的電話 Nokia
這個手機使用了全面的技術 —— 嵌入式 JVM
GPRS 和藍牙
但是它也遭遇了所有智能手機都苦惱的問題 —— 有限的屏幕實際使用區
有些 Web 站點支持基於手機的浏覽器
而且嵌入式浏覽器也試圖在小小的屏幕上有效地渲染頁面
但是
在電話屏幕上查看典型的 Web 頁面
就像要把一頭大象強行塞進車後座一樣(其中的每個參與者都會感到失望
包括您
車和大象)
Sam 構建了一個簡單的
優雅的解決方案
從他喜歡的 Web 站點上對數據進行屏幕搜集
然後把數據重新格式化
在小屏幕上顯示
新方法 從 HTML 文檔提取數據的方法有許多種
但是我真的很喜歡 Sam 采用的方法
既把 XQuery 當作屏幕搜集工具(從頁面中提取相當的數據)
又把它當作樣式表工具(重新格式化數據
以便數據適應頁面
不需要進行頁面滾動)
只要少量基礎設施和一些非常簡單的 XQuery 表達式
就可以從大量數據源提取出相關數據 —— 例如交通
天氣和財務報價等
並在電話上完好地顯示數據
我過去經常處於這種情況
對 HTML 頁面進行屏幕搜集對某些特定問題來說似乎是可行的方案
但是幾乎沒有用於屏幕搜集的 Java 工具包
有許多 HTML 解析工具
但它們通常缺少足夠的抽象能力(把屏幕搜集代碼弄得亂七八糟)
大量不符合 HTML 規范的應用限制了它們
它們也無法處理那些結構可能隨時間發生變化的
動態生成的頁面
為了彌補質量低下的 HTML 和豐富的 XML 處理工具之間的空白
首先要把 HTML 轉換成 XML
許多工具有助於完成這項工作
JTidy 工具包做得很好
可以使這項工作變得輕松一些
JTidy 的設計目標是讀入典型質量(即很糟)的 HTML 並輸出更整潔的結果(有選項可供選擇)
它還提供了一個 DOM 接口
用來遍歷能夠發送給 XML 解析器的 HTML 文檔
清單
中的代碼將從 InputStream 中讀取 HTML 文檔
並生成文檔的 DOM 表示
清單 用 JTidy 把 HTML 轉換成 XML 兼容的 DOM Tidy tidy = new Tidy();
tidy
setQuiet(true);
tidy
setShowWarnings(false);
Document tidyDOM = tidy
parseDOM(inputStream
null);
用這個簡單的轉換
就差不多能把每個 Web 頁面都當作 XML 文檔進行處理
還能用自己喜歡的任何 XML 工具(比如 SAX
XSL
XPath
等等)提取數據
雖然 XSL 可能是很明智的選擇(因為其設計目標就是為了從 XML 文檔中提取信息並轉換這些信息
以便顯示它們)
但是如果不了解 XSL 的話
它的學習曲線就很難掌握
即使是最簡單的 XSL 轉換也復雜得讓人心煩
XPath 是處理信息提取的一個好選擇 —— XSL 和 XQuery 都用它進行內容選擇
可以很容易地使用 XPath 把需要的數據提取出來
然後對 HTML 進行格式化
但是 XQuery 會讓這項工具更加容易
XQuery簡介 XQuery 的設計目標是從可能非常大的 XML 數據集中提取數據
輸入的數據集不必是 XML 文檔
雖然它可能是 XML 文檔
但是也可能是已經編入索引並保存在 XML 數據庫中的文檔集合
甚至是一組關系數據庫中的表
像 SQL 一樣
XQuery 包含從多個數據集中提取數據
匯總數據
聚合數據和連接數據的函數
就像 JSP
ASP 或 Velocity 這樣的表示性模板語言一樣
XQuery 把兩個域(表示域和計算域)中的元素組合成一種組合語法
結果
所有 XML 文檔都自動成為有效的 XQuery 表達式
並對自身進行評估
XQuery 還包含一些語言語句(language statement)
例如
for
和
let
它們可以與 XML 元素混合使用
清單
顯示了一個示例 XML 文檔 bib
xml
它表示一個書目
然後我們將介紹一些快速的 XQuery 表達式
讓您對 XQuery 能夠做什麼形成一種認識
最後我們將再轉到屏幕搜集的示例上
要全面介紹 XQuery 的語法和使用情況可能要用幾百頁的篇幅
有關更詳細的參考材料和示例
請參閱 參考資料 小節
清單 示例 XML 書目 <bib>
<book year=
>
<title>TCP/IP Illustrated</title>
<author><last>Stevens</last><first>W
</first></author>
<publisher>Addison
Wesley</publisher>
<price>
</price>
</book>
more books
</bib>
清單
顯示了一個 XQuery 表達式
它選擇 Addison
Wesley 在
年以後出版的所有書籍
提取它們的標題
並把標題格式化成前面有項目符號的(<ul>)列表
大括號表示從
表示模式
(數據直接傳遞到輸出
例如 <ul> 和 <li> 標簽)到
代碼模式
的切換
然後在 return 子句之後立即進行從
代碼模式
到
表示模式
的隱式切換
清單 根據查詢參數選擇圖書標題的 XQuery 表達式 <ul>
{
for $b in doc(
bib
xml
)/bib/book
where $b/publisher =
Addison
Wesley
and $b/@year >
return
<li>{ data($b/title) }</li>
}
</ul>
查詢語法引入了
for
通常稱之為
Flower 表達式
(來自 FLWOR
是 for
let
where
order
return 的縮寫)
該語法從文檔中選擇一系列 XML 節點
在該例中
用 XPath 選取了來自 bib
xml 文檔的 <book> 節點集
然後進一步過濾出與指定查詢參數(出版商是 Addison
Wesley
出版日期是
年之後)匹配的節點
對於選出的每個節點
將在 return 子句中計算表達式
在這裡是標記(<li> 標簽)與代碼(提取出每個 <book> 節點的 <title> 元素的內容)的混合
這個簡單的 XQuery 示例描述了 XQuery 的幾個方面 —— 某一文檔中表示與代碼的混合
XPath 的運用
子條件的運用($b 引用)
非凡的查詢表達式
XQuery 函數(data())
還有一個事實
輸出文檔的結構不必與輸入文檔的結構匹配
就在這個相當緊湊的
讀起來不是很難的查詢中
孕育著強大的處理能力
清單
顯示了一個更簡單的 XQuery 表達式
它把書目中不同出版商的數量
在一個 <count> 元素中輸出
像前一個示例一樣
它用 XPath 表達式選擇一組節點
然後用 XQuery 函數選擇惟一值
並計算節點的數量
它通過運算獲得一個數字 —— bib
xml
即文檔中不同出版商的數量
清單 計算不同出版商數量的 XQuery 表達式 <count>
{
let $d := distinct
values(doc(
bib
xml
)/book/publisher)
return count($d)
}
</count>
這些示例只是 XQuery 能夠執行的各種查詢類型的很少一部分
提供這些例子僅僅是為了讓您對使用 XQuery 能夠做的事情有些感覺
以及提示您如何才能用 XQuery 把 XML 文檔轉換成自己選擇的格式
雖然 XQuery 的大部分功能主要用於查詢大型文檔或者其他數據源
但是也可以使用 XQuery 非常簡單的子集來對 HTML 文檔進行屏幕搜集
為各種應用程序提取出需要的數據
例如在屏幕大小有限的設備(例如蜂窩電話)上顯示有關的數據
或者創建一個 DIY 的門戶網站
聚集並顯示來自多個站點的數據
用 XQuery 進行屏幕搜集 對 Web 頁面做屏幕搜集的許多挑戰之一是
它們通常沒有可以自我標識的結構
而且它們的結構可能隨著站點內容的編輯而變化
甚至有可能根據不同的請求
在頁面中插入不同的動態內容(例如廣告內容)
因此
對於頁面中哪一部分的內容與要提取的數據相對應
通常不得不進行猜測
股票價格 現在
讓我們從提取 Yahoo! 財經頁面中 IBM 股票的當前價格開始()
這個頁面上有許多材料 —— 新聞標題
廣告
財經數據
等等
但是我想要的是股票的價格數據
它放在一個表格單元格中
靠近包含
Last Trade
的單元格
清單
中的查詢語句將選擇所有文本內容中包含
Last Trade
的 <td> 節點
然後為每個節點(希望只有一個)輸出一個包含後續<td> 節點內容的表格行
內容是用 return 子句中的 data() 函數提取的
否則
不僅僅會得到 <td> 節點中的文本
還會得到所有的標記
(在這個查詢中
惟一包含技巧的部分是 text()[
] 這個部分
在這裡
text() 函數匹配的是 <td> 元素中的所有元素 —— 在這個例子只有一個元素
但 XQuery 並不知道這一點 —— 所以必須進一步告訴它在進行文本匹配之前
必須選擇第一個文本節點)
只要頁面包含一個表格單元格的文本是
Last Trade
而且後續的單元格包含的是股票價格
那麼
即使頁面的結構隨意變化
也不會造成查詢失敗
清單 從 Yahoo! 財經提取股票報價的 XQuery 表達式 <table>
{
for $d in //td
where contains($d/text()[
]
Last Trade
)
return <tr><td> { data($d/following
sibling::td) } </td></tr>
}
</table>
天氣 現在來試一下另外一個頁面
Yahoo! 天氣頁面包含許多 portlet 面板
我想提取上面所列城市的名稱
溫度和圖標
(如果登錄 Yahoo! 天氣頁面
則屏幕上會顯示出在
我的 Yahoo!
中所選城市的天氣
否則會顯示一些主要大城
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19203.html