這篇“Thinking XML”專欄文章演示了如何將從多個 XML 源文檔中收集的元數據合並到用於有效查詢的單個“資源描述框架”(Resource Description Frame (RDF))模型中。專欄作家 Uche Ogbuji 在前一篇中介紹了如何一起使用 XML 和 RDF 來進行知識管理,本文是上一篇的繼續,在本文中他論述了基於用從現有的 XML 格式獲取的數據組成 RDF 模型的技術。該文的中心是一個示例,在該示例中,擴展基於 Web 的問題跟蹤器(最初開發它來操縱 XML 格式的應用程序數據)來利用 RDF。XSLT 和 Python 樣本代碼清單演示了將來自 XML 文件的元數據聚合到單個 RDF 模型(一個使用 XSLT ,另一個使用 RDF)以及簡單 RDF 查詢的示例。
在本專欄的前一篇文章中,即 知識管理的基本 XML 和 RDF 技術,第一部分(在繼續閱讀之前,可能需要重新回顧一下),介紹了一個基於收集的數據(以 XML 格式表示)的問題跟蹤器應用程序示例。然後演示了如何使用 XSLT 從應用程序數據中抽取 RDF。
本專欄文章通過演示如何組合由轉換每個 XML 源文檔所創建的離散的 RDF 片段來完成這些事情。同時還演示了一些基本的查詢技術。
資源批量派生
在上篇專欄文章中,完成了一個從 XML 源文件中抽取作為序列化格式的 RDF 的練習。然而,知識管理的大多數有用之處在於操縱 RDF 語句的抽象模型,而不是個別序列化文件。RDF 模型是圖形結構,該結構可以非常簡單也可以令人難以置信的復雜。(事實上,極端情況下,Semantic Web 的設想是要創建至少同當前 Web 一樣大的 RDF 模型。如果實現該模型的話,它可能是曾經在使用中的最大的計算機數據結構。)
迄今為止,問題跟蹤器的示例是由一組 XML 文件組成的,這些文件是 RDF 模型各部分的序列化。每一部分就象七巧板中的一塊,如果將它們組合到一塊兒,將表示問題跟蹤器應用程序中所有數據的 RDF 模型。每一部分本身並不十分重要,因此下一步來討論如何創建一幅完整的“圖畫”,該“圖畫”才是知識管理的真正價值所在。
使用 XSLT 進行批量轉換
拼裝該“圖畫”的一種選擇是根本不處理七巧板塊,而是使用 XSLT 一勞永逸地生成該“圖畫”。可以編寫一個轉換,該轉換收集每個 XML 問題文檔,並運用前一篇文章中所描述的 RDF 轉換處理,然後將每次轉換結果累積到一個序列化 RDF 結果。要做到這一點,需要利用標准的 document() XSLT 函數,該函數可以讀入任意的文檔,這些文檔是作為用於轉換的補充性 XML 源文檔。
document() 函數需要知道所要讀入文檔的准確名稱。XSLT 沒有(比如說)通過使用通配符來讀入多個文檔的標准機制。可以通過許多方法解決這一問題(包括借助於 XSLT 擴展,該擴展允許通過通配符或其它機制來批量裝入文檔)。然而,因為這不是本示例的中心問題,所以選擇創建帶有每個問題文檔清單的集中(hub) XSLT 文檔這項折中方案。清單 1 issue-hub.XML 用指定的兩個問題文檔(在前一篇專欄文章中介紹過)來演示集中文檔是什麼樣子的。
清單 1:帶有每個應用程序數據文檔的問題文檔清單的集中 XML 文檔。
issue1.XML
issue2.XML
一旦有了集中文檔,就可以將 清單 2metadata-batch.xslt 中的轉換應用到作為源文檔的集中文檔中以從所有列出的問題中創建聚合 RDF。
清單 2 看起來可能非常熟悉;其中大多數同前一篇文章中的轉換相同。這是因為我很留意 XML 結構的模塊化處理。根模板現在從集中文檔中查找每個 issue 元素(注意,這裡使用空名稱空間)。然後它從每個元素內容中指定的文件名裝入文檔。注意,這裡使用 document(.)/* 而不僅僅是 document(.) 。這種構造防止處理器匹配問題文檔的根節點,這會結束調用打算用於集中文檔根節點的模板。那些正是本專欄文章的批量轉換中的唯一區別。
可以使用任何 XSLT 處理器來執行該轉換。我使用 4XSLT 來執行轉換,如下所示:
$ 4xslt -o issues.rdf issue-hub.XML metadata-batch.xslt
該轉換導致 issues.rdf 中的序列化 RDF,它包含來自 issue1.xml 和 issue2.XML 中的元數據。
使用 RDF 工具進行批量轉換
將多個 XML 文檔聚合到單個 RDF 模型中的另一種選擇是(如前一篇文章所述)分別構建每個 RDF 文檔,然後使用 RDF 解析器將所有的文檔解析成單個抽象模型。例如,通過對從前面一篇專欄文章中的示例問題文件中生成的兩個 RDF 文件使用 4RDF,將得到清單 3。
清單 3:從 XML 文件中派生,並由 RDF 解析器(這裡是 4RDF)分析的兩個 RDF 問題文件的批量轉換結果
$ 4rdf -d 1.rdf 2.rdf
The following is a list of resulting tuples, each in the form "subject,
predicate, object".
[
("http://meta.rdfinference.org/ril/issue-tracker/ril-20010502",
"http://XMLns.rdfinference.org/ril/issue-tracker#issue",
"#i2001030423"),
("#anonymous:e08-e06-30b-90d-a060005104", "id", "i2001030423"),
("#anonymous:e08-e06-30b-90d-a060005104",
"http://XMLns.rdfinference.org/ril/issue-tracker#author",
"http://users.rdfinference.org/ril/issue-tracker#uogbuji"),
("#anonymous:1040d07-20b-909-407-b0e0402205",
"http://XMLns.rdfinference.org/ril/issue-tracker#author", "Alexandre
Fayolle "),
("#anonymous:1040d07-20b-909-407-b0e0402205",
"http://XMLns.rdfinference.org/ril/issue-tracker#body", "The
abbreviation in listing 8 doesn't seem necessary to Nico Chauvat or
me."),
("#anonymous:e08-e06-30b-90d-a060005104",
"http://XMLns.rdfinference.org/ril/issue-tracker#comment",
"#anonymous:1040d07-20b-909-407-b0e0402205"),
("#anonymous:60e0b06-50a-80c-90c-50a0a08602",
"http://XMLns.rdfinference.org/ril/issue-tracker#author",
"http://users.rdfinference.org/ril/issue-tracker#uogbuji"),
("#anonymous:60e0b06-50a-80c-90c-50a0a08602",
"http://XMLns.rdfinference.org/ril/issue-tracker#assignment",
"http://users.rdfinference.org/ril/issue-tracker#uogbuji"),
("#anonymous:e08-e06-30b-90d-a060005104",
"http://XMLns.rdfinference.org/ril/issue-tracker#action",
"#anonymous:60e0b06-50a-80c-90c-50a0a08602"),
("http://meta.rdfinference.org/ril/issue-tracker/ril-20010502",
"http://XMLns.rdfinference.org/ril/issue-tracker#issue",
"#i2001042003"),
("#anonymous:2030002-506-10a-201-f090809c08", "id", "i2001042003"),
("#anonymous:2030002-506-10a-201-f090809c08",
"http://XMLns.rdfinference.org/ril/issue-tracker#author",
"http://users.rdfinference.org/ril/issue-tracker#nchauvat"),
("#anonymous:c000706-b0b-d02-d0a-e050c0460b",
"http://XMLns.rdfinference.org/ril/issue-tracker#author", "Alexandre
Fayolle "),
("#anonymous:c000706-b0b-d02-d0a-e050c0460b",
"http://XMLns.rdfinference.org/ril/issue-tracker#body", "I agree"),
("#anonymous:2030002-506-10a-201-f090809c08",
"http://XMLns.rdfinference.org/ril/issue-tracker#comment",
"#anonymous:c000706-b0b-d02-d0a-e050c0460b"),
("#anonymous:b0c0c00-800-2-c08-20a0d0f209",
"http://XMLns.rdfinference.org/ril/issue-tracker#author",
"http://users.rdfinference.org/ril/issue-tracker#uogbuji"),
("#anonymous:b0c0c00-800-2-c08-20a0d0f209",
"http://XMLns.rdfinference.org/ril/issue-tracker#assignment",
"http://users.rdfinference.org/ril/issue-tracker#uogbuji"),
("#anonymous:2030002-506-10a-201-f090809c08",
"http://XMLns.rdfinference.org/ril/issue-tracker#action",
"#anonymous:b0c0c00-800-2-c08-20a0d0f209"),
]
清單 3 中第一行裡的 -d 選項告訴 $RDF 將 subject/predicate/object 三元組從抽象 RDF 模型轉儲到命令行,該命令行顯示清單 3 的剩余部分.
一旦有了問題跟蹤器的完整 RDF 模型,可能希望以一種更易於閱讀的格式而不是清單 3 中所顯示的那種格式來查看它。例如,如果以清單 3 中的聚合 RDF 文件為例,並使用 Dan Brickley 的 RDF 可視化工具(請參閱 參考資料)來處理它,將得到一幅類似 圖 1的圖表。對於一般的膝上電腦屏幕來說,它可能太寬,因此請單擊鏈接在單獨的窗口中打開圖表。
圖 1 中的可視化顯示的好處在於:可以立即辨別出某種模式,譬如用戶“uogbuji”的投稿和職責。當然,我必須承認 RDF 中隨同 URI 而來的冗長的標識符妨礙了這一清晰性。
跨系統查詢
擁有可用元數據的 RDF 模型的另外一個立桿見影的好處在於:它使許多系統級查詢(相對於編寫針對一組 XML 文檔的 XPath 查詢,或將數據集中到專用數據結構以用於查詢)變得更簡單更一般。雖然,“XML 查詢語言”(XQuery)和 XML 資源庫供應商提供的專用文檔收集查詢工具的出現也幫助解決了這一需求,但是現在可以使用 RDF,至少這種基本模型是標准化的。
遺憾的是,RDF 模型的查詢還不是標准化的,這是 RDF 社區需要填補的非常重要的漏洞。幸運的是,由於 RDF 模型的簡單性,所以非常容易通過基本模式匹配來構建幾乎所有形式的查詢。這是 4RDF 中所使用的基本方法,其中,查詢就是就是查找語句三元組的過程,該三元組匹配給定的主語、謂語和賓語模式。作為示例,請看表 1 中剛創建的部分統一模型。
表 1 .來自問題跟蹤器數據的一些語句
主語:
#anonymous:a0d010d-f0c-706-20e-80a0407606
謂語:
http://XMLns.rdfinference.org/ril/issue-tracker#body
賓語:
Organize a vote on this topic
主語:
#anonymous:a0d010d-f0c-706-20e-80a0407606
謂語:
http://XMLns.rdfinference.org/ril/issue-tracker#assign-to
賓語:
http://users.rdfinference.org/ril/issue-tracker#uogbuji
主語:
#anonymous:402000d-403-309-c01-9080a205
謂語:
http://XMLns.rdfinference.org/ril/issue-tracker#body
賓語:
Correct all to use the "0/1" form in the next draft.
主語:
#anonymous:402000d-403-309-c01-9080a205
謂語:
http://XMLns.rdfinference.org/ril/issue-tracker#assign-to
賓語:
http://users.rdfinference.org/ril/issue-tracker#uogbuji
有了這些三元組,就會非常容易地明白別人是如何會簡單地詢問如下問題:“給用戶 uogbuji 分配的操作標識是什麼?”以及“每個操作的主體是什麼?”。將第一個問題轉換成查找匹配下列模式的三元組,這裡的“*”是匹配任何值的通配符:
主語
謂語
賓語
*
http://XMLns.rdfinference.org/ril/issue-tracker#assign-to
http://users.rdfinference.org/ril/issue-tracker#uogbuji
對此的響應之一是帶有主語“#anonymous:a0d010d-f0c-706-20e-80a0407606”(表示匹配的操作標識)的語句。注意,該標識是由 4RDF 為匿名資源(如果資源沒有標識,則由應用程序顯式地分配一個)生成的特殊標識。構成全球唯一標識符(UUID)的一個十六進制值緊跟在“anonymous”序列之後。有了這個標識,第二個示例問題相當於匹配下列模式,該模式返回帶有賓語“Organize a vote on this topic”的語句:
主語
謂語
賓語
#anonymous:a0d010d-f0c-706-20e-80a0407606
http://XMLns.rdfinference.org/ril/issue-tracker#body
*
建立在這一簡單的想法上,幾乎任何形式的 RDF 查詢(甚至關系型(SQL)查詢和對象(OQL)查詢的等價查詢)都是可能的。
編碼 RDF 查詢
清單 4 中的 Python 程序 query1.py 通過模式匹配、讀入 issues.rdf 文件以及打印 uogbuji 的所有任務的主體,使用 4RDF 來運用這項查詢技術。(如果沒有使用 Python,當然可以使用其它 RDF 查詢工具,譬如 RDFDb、Jena 或列在 參考資料中“Dave Beckett 的 RDF 資源指南”中的工具。)
清單 4:演示一個 RDF 模型簡單查詢的 Python 程序。
from Ft.Rdf import Util
#Returns an RDF model object, and the database instance it uses for
#persistence (in our case, it's just a memory data structure)
model, db = Util.DeserializeFromUri('issues.rdf')
db.begin()
USER_ID_BASE = 'http://users.rdfinference.org/ril/issue-tracker#'
IT_SCHEMA_BASE = 'http://XMLns.rdfinference.org/ril/issue-tracker#'
print 'Actions assigned to uogbuji:'
#None is used as the wild-card
matching_statements = model.complete(None,
IT_SCHEMA_BASE+'assign-to',
USER_ID_BASE+'uogbuji'
)
for statement in matching_statements:
id = statement.subject
matching_statements = model.complete(id,
IT_SCHEMA_BASE+'body',
None
)
body = matching_statements[0].object
print "*", body
db.commit()
在清單 4 中,首先從 issues.rdf 文件中讀取(解除序列化)RDF;然後執行查詢以查找表示指派給 uogbuji 哪些操作的語句。然後使用每個語句的主語(操作標識)來查詢每個操作的主體。如果安裝了 Python 和 4Suite,運行和下面相同的示例:
$ Python query1.py
Actions assigned to uogbuji
* Organize a vote on this topic
* Correct all to use the "0/1" form in the next draft.
這是 RDF 模型查詢的最底級別,因此有一點麻煩。清單 5 中的 Python 程序 query2.py 通過直接查詢相關主語和賓語等而走了一些捷徑。其結果就是清單 4 中同一個功能的更簡單的版本。
清單 5:簡化查詢代碼
from Ft.Rdf import Util
#Returns an RDF model object, and the database instance it uses for
#persistence (in our case, it's just a memory data structure)
model, db = Util.DeserializeFromUri('issues.rdf')
db.begin()
USER_ID_BASE = 'http://users.rdfinference.org/ril/issue-tracker#'
IT_SCHEMA_BASE = 'http://XMLns.rdfinference.org/ril/issue-tracker#'
print 'Actions assigned to uogbuji:'
actions = Util.GetSubjects(model, IT_SCHEMA_BASE+'assign-to',
USER_ID_BASE+'uogbuji')
for action in actions:
body = Util.GetObject(model, action, IT_SCHEMA_BASE+'body')
print "*", body
db.commit()
清單 5 仍然包含兩種級別的查詢;使用結合多個查詢的單個請求將更方便和更有效。(通過使用“RDF 推論語言(RDF Inference Language (RIL))”,那也是可能的,“RDF 推論語言”將在本專欄的後續文章中討論。順便提一下,示例問題跟蹤器數據中參考了 RIL 開放規范草稿。)清單 6 演示了在使用或不使用 API 捷徑情況下,執行基本查詢的結果。
清單 6:執行同清單 5 相同任務的簡化的查詢會話
$ Python query2.py
Actions assigned to uogbuji
* Organize a vote on this topic
* Correct all to use the "0/1" form in the next draft.
RDF 查詢不必涉及元數據語句組件的精確匹配。大多數的查詢語言提供大量的靈活性。作為一個示例, 清單 7(query3.py)中的 Python 程序使用基於正則表達式的查詢來查找所有指派給 uogbuji 的操作(該操作的主體包含字符串“vote”)。
同樣,通過使用 RIL,清單 7 中的代碼將是一個簡單查詢而且更有效(將在下一篇專欄文章裡講述)。清單 8 演示了查詢中正則表達式的使用。
清單 8:演示在查詢中使用正則表達式的會話
$ Python query3.py
Actions assigned to uogbuji
* Organize a vote on this topic
下一次……
本專欄文章演示了如何從示例問題跟蹤器應用程序將各部分組成完整的 RDF 模型,並且舉例說明了該模型的基本查詢。在下篇文章中將探討一種豐富特性,可以利用 RDF 的強大功能來低成本地利用該特性。