Jena文档《An Introduction to RDF and the Jena RDF API》的译文
前言
本文是一篇對W3C的資源描述框架(RDF)和 Jena(一個Java的RDF API)的教程性介紹. 本文是為那些不熟悉RDF的, 以及那些通過建立原形可以達到最好學習效果的, 或是因為其他原因希望能快速操作Jena的程序員而寫的. 我們假設讀者在閱讀本文前已具有一定的XML和Java知識.
如果讀者在沒有理解RDF數據模型的基礎上就迅速進入操作階段,往往會導致失敗和失望. 然而,如果光學習數據模型又是十分枯燥乏味的, 并常常會導致曲折的形而上學的難題. 更好的學習辦法是在理解數據模型的同時練習操作它. 可以先學習一點數據模型再動手試一試.然后在學習一點再試一試. 這樣一來就能達到理論實踐相結合的效果.數據模型本身十分簡單,所以學習過程不會太長.
RDF具有XML的語法, 所以許多熟悉XML的人就會認為以XML語法的形式來思考RDF. 然而, 這是不對的. RDF應該以它數據模型的形式來被理解. RDF數據可是用XML來表示, 但是理解數據模型的重要性更在理解此語法重要性之上.
Jena API的一個運行例子, 包括本教程中所有例子的工作源代碼都可以在http://www.hpl.hp.com/semweb/下載.
________________________________________
目錄
1. 導言
2. 陳述Statements
3. RDF寫操作
4. RDF讀操作
5. Jena RDF 包
6. 操縱模型
7. 查詢模型
8. 對模型的操作?
9. 容器Containers
10. 關于Literals和數據類型的更多探討
11. 術語表
________________________________________
導言
資源描述框架是(RDF)是描述資源的一項標準(在技術上是W3C的推薦標準). 什么是資源? 這實在是一個很難回答的問題, 其精確的定義目前尚在爭論中. 出于我們的目的, 我們可以把資源想象成任何我們可以確定識別的東西. 在本教程中,讀者你本身就是一個資源, 而你的主頁也是一個資源, 數字1和故事中巨大的白鯨都是資源.
在本教程中, 我們的例子會圍繞人們展開. 假設人們會使用VCARDS, 而VCARD將由RDF表示機制來描述. 我們最好把RDF考慮成由結點和箭頭的形式構成的圖. 一個簡單的vcard在RDF中可能看起來是這樣的:
資源John Smith在圖中用橢圓表示, 并被一個統一資源定位符(URI) 所標識, 在本例中是"http://.../JohnSmith"). 如果你想要通過你的瀏覽器來訪問這個資源的話,你很有可能會失敗. 四月的愚人節笑話并不經得起考驗, 相反如果你的瀏覽器把John Smith傳遞到你的桌面的話, 你才該感到驚訝. 如果你并不熟悉URI's的話, 你可以把它們想象成簡單的陌生名字.
資源擁有屬性(property). 在這些例子中, 我們對John Smith名片上出現的那些屬性很感興趣.圖1只顯示了一個屬性, John Smith的全名. 屬性是由標有屬性名的箭頭表示的. 屬性的名字也是一個URI, 但是由于URI十分冗長笨重, 所以圖中將它顯示為XML qname的形式. 在':'之前的部分稱為命名空間前綴并表示了一個命名空間. 在':'之后的部分稱為局部名, 并表示在命名空間中的一個名字. 在寫成RDF XML形式時, 屬性常常以qname的形式表示, 這是一個在圖形和文本中的簡單的縮寫方法. 然而, 嚴格地講, 屬性應該用URI來標識. 命名空間前綴:局部名的形式是一種命名空間連接局部名的URI縮寫. 當瀏覽器訪問時, 用并沒有強制屬性的URI必須指向一些具體的事物.
每個屬性都有一個值. 在此例中, 值為一個文本(literal), 我們現在可以把它看成一個字符串.文本在圖中顯示為長方形.
Jena是一個Java API, 我們可以用它來創建和操縱諸如上述例圖的RDF圖. Jena設有表示圖(graph), 資源(resource), 屬性和文本(literal)的對象類. 表示資源, 屬性和文本的接口分別稱為Resource, Property, 和Literal. 在Jena中, 一個圖(graph)被稱為一個模型并被Model接口所表示.
創建上述例圖或稱為上述模型的代碼很簡單:
// some definitions
static String personURI??? = "http://somewhere/JohnSmith";
static String fullName???? = "John Smith";
?
// create an empty Model
Model model = ModelFactory.createDefaultModel();
// create the resource
Resource johnSmith = model.createResource(personURI);
// add the property
johnSmith.addProperty(VCARD.FN, fullName);
這些代碼先定義了一些常量, 然后使用了ModelFactory類中的createDefaultMode()方法創建了一個空的基于內存存儲的模型(Model 或 model). Jena還包含了Model接口的其他實現方式. 例如, 使用關系數據庫的, 這些類型 Model接口也可以從ModelFactory中創建.
于是John Smith這個資源就被創建了, 并向其添加了一個屬性. 此屬性由一個"常" ("constant")類VCARD提供, 這個類保存了在VCARD模式(schema)中所有定義的表示對象. Jena也為其他一些著名的模式提供了常類的表示方法, 例如是RDF和RDF模式, Dublin 核心標準和DAML.
創建資源和添加屬性的代碼可以寫成更緊湊的層疊形式:
Resource johnSmith =
??????? model.createResource(personURI)
???????????? .addProperty(VCARD.FN, fullName);
這個例子的工作代碼可以在Jena發布的材料的教程包中的Tutorial1中找到. 作為練習, 你自己可以獲得此代碼并修改其以創建一個簡單VCARD.
現在讓我們為vcard再增加一些更詳細的內容, 以便探索更多的RDF和Jena的特性.
在第一個例子里, 屬性值為一個文本. 然而RDF屬性也可以采用其他的資源作為其屬性值. 下面這個例子使用常用的RDF技術展示了如何表示John Smith名字的不同部分:
在這里我們增加了一個新的屬性, vcard:N, 來表示John Smith名字的結構. 這個模型有幾點有趣之處. 注意屬性vcard:N使用一個資源作為起屬性值. 同時注意代表復合名字的橢圓并沒有URI標識. 它被認為是一個空白結點(blank Node).
創建此例的Jena代碼也十分簡單. 首先是一些聲明和對空模型的創建.
// some definitions
String personURI??? = "http://somewhere/JohnSmith";
String givenName??? = "John";
String familyName?? = "Smith";
String fullName???? = givenName + " " + familyName;
// create an empty Model
Model model = ModelFactory.createDefaultModel();
// create the resource
//?? and add the properties cascading style
Resource johnSmith
? = model.createResource(personURI)
???????? .addProperty(VCARD.FN, fullName)
???????? .addProperty(VCARD.N,
????????????????????? model.createResource()
?????????????????????????? .addProperty(VCARD.Given, givenName)
?????????????????????????? .addProperty(VCARD.Family, familyName));
此例的工作代碼可以在Jena發布材料的教程包的Tutorial2中得到.
________________________________________
陳述
RDF模型中的每一個箭頭表示為一個陳述(statement). 每一個陳述聲明了關于某個資源的某個事實. 一個陳述有三部分組成.
主體, 也就是箭頭的出發的資源.
謂詞, 也就是標識箭頭的屬性.
客體, 也就是箭頭所指向的那個資源或文本.
一個陳述有時也叫做一個三元組的原因就是它由三部分組成.
一個RDF模型(譯者注: 指Jena中的接口Model)是由一組陳述所組成的. 在Tutorial2中, 每調用一次addProperty函數就會在模型中增加另一個陳述. (因為一個模型是由一組陳述組成的, 所以增加一個重復的陳述并不會產生任何意義.) Jena模型接口定義了一個listStatements()方法, 此方法會返回一個StmtIterator類型的變量. StmtItor是Java中Iterator的一個子類型, 這個StmtIterator變量重復迭代了該接口模型中的所有陳述. StmtIterator類型中有一個方法nextStatement(), 該方法會從iterator返回下一個陳述. (就和next()返回的一樣, 但是已將其映射為Statement類型). 接口Statement提供了訪問陳述中主體, 謂詞和客體的方法.
現在我們會用使用那個接口來擴展Tutorial2, 使起列出所有的創建的陳述并將它們打印出來. 此例完整的代碼可以在Tutorial3中找到.
// list the statements in the Model
StmtIterator iter = model.listStatements();
// print out the predicate, subject and object of each statement
while (iter.hasNext()) {
??? Statement stmt????? = iter.nextStatement();? // get next statement
??? Resource? subject?? = stmt.getSubject();???? // get the subject
??? Property? predicate = stmt.getPredicate();?? // get the predicate
??? RDFNode?? object??? = stmt.getObject();????? // get the object
??? System.out.print(subject.toString());
??? System.out.print(" " + predicate.toString() + " ");
??? if (object instanceof Resource) {
?????? System.out.print(object.toString());
??? } else {
??????? // object is a literal
??????? System.out.print(" /"" + object.toString() + "/"");
??? }
??? System.out.println(" .");
}?
因為一個陳述的客體可以是一個資源也可以是一個文本. getObject()方法會返回一個類型為RDFNode的客體, RDFNode是Resource和Literal類共同的超類. 為了確定本例中的客體確切的類型, 代碼中使用 instanceof來確定其類型和相應的處理.
運行后, 此程序回產生與此相似的輸出:
http://somewhere/JohnSmith?http://www.w3.org/2001/vcard-rdf/3.0#N anon:14df86:ecc3dee17b:-7fff.
anon:14df86:ecc3dee17b:-7fff?http://www.w3.org/2001/vcard-rdf/3.0#Family? "Smith".
anon:14df86:ecc3dee17b:-7fff?http://www.w3.org/2001/vcard-rdf/3.0#Given? "John" .
http://somewhere/JohnSmith?http://www.w3.org/2001/vcard-rdf/3.0#FN? "John Smith".
現在你明白了為什么模型構建會更加清晰. 如果你仔細觀察, 就會發現上面每一行都由三個域組成, 這三個域分別代表了每一個陳述的主體, 謂詞和客體. 在此模型中有四個箭頭, 所以會有四個陳述. "anon:14df86:ecc3dee17b:-7fff"是有Jena產生的一個內部標識符, 它不是一個URI, 也不應該與URI混淆. 它只是Jena處理時使用的一個內部標號.
W3C的RDF核心工作小組定義了一個類似的表示符號稱為N-三元組(N-Triples). 這個名字表示會使用"三元組符號". 在下一節中我們會看到Jena有一個內置的N-三元組寫機制(writer).
________________________________________
寫RDF
Jena設有讀寫XML形式的RDF方法. 這些方法可以被用來將一個RDF模型保存到文件并在日后重新將其讀回.
Tutorial3創建了一個模型并將其以三元組的形式輸出. Tutorial4對Tutorial3做了修改, 使其將此模型以RDF XML的形式輸出到標準輸出流中. 這個代碼依然十分簡單: model.write可以帶一個OutputStream的參數.
// now write the model in XML form to a file
model.write(System.out);
應該有類似的輸出:
<rdf:RDF
? xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
? xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
>
? <rdf:Description rdf:about='http://somewhere/JohnSmith'>
??? <vcard:FN>John Smith</vcard:FN>
??? <vcard:N rdf:nodeID="A0"/>
? </rdf:Description>
? <rdf:Description rdf:nodeID="A0">
??? <vcard:Given>John</vcard:Given>
??? <vcard:Family>Smith</vcard:Family>
? </rdf:Description>
</rdf:RDF>
W3C的RDF規格說明書規定了如何用 XML的形式來表示RDF. RDF XML的語法十分復雜. 讀者可以在RDF核心工作小組制定的RDF入門篇(primer)中找到更詳細的指導. 但是不管怎么樣, 讓我們先迅速看一下應該如何解釋上面的RDF XML輸出
RDF常常嵌入在一個<rdf:RDF>元素中. 如果有其他的方法知道此XML是RDF的話,該元素是可以不寫的. 然而我們常常會使用它. 在這個RDF元素中定義了兩個在本文檔中使用的命名空間. 接下來是一個<rdf:Description>元素, 此元素描述了URI為"http://somewhere/JohnSmith"的資源. 如果其中的rdf:about屬性被省略的話, 這個元素就表示一個空白結點.
<vcard:FN>元素描述了此資源的一個屬性. 屬性的名字"FN"是屬于vcard命名空間的. RDF會通過連接命名空間前綴的URI和名字局部名"FN"來形成該資源的URI "http://www.w3.org/2001/vcard-rdf/3.0#FN". 這個屬性的值為文本"John Smith".
<vcard:N>元素是一個資源. 在此例中, 這個資源是用一個相對URI來表示的. RDF會通過連接這個相對URI和此文檔的基準URI來把它轉換為一個絕對URI.
但是, 在這個RDF XML輸出中有一個錯誤, 它并沒有準確地表示我們所創建的模型. 模型中的空白結點被分配了一個URI. 它不再是空白的了. RDF/XML語法并不能表示所有的RDF模型. 例如它不能表示一個同時是兩個陳述的客體的空白結點. 我們用來寫這個RDF/XML的'啞'writer方法并沒有試圖去正確的書寫這個模型的子集, 雖然其原本可以被正確書寫. 它給每一個空白結點一個URI, 使其不再空白.
Jena有一個擴展的接口, 它允許新的為不同的RDF串行化語言設計的writer可以被輕易地插入. 以上的調用會激發一個標準的'啞'writer方法. Jena也包含了一個更加復雜的RDF/XML writer, 它可以被用攜帶另一個參數的write()方法所調用.
// now write the model in XML form to a file
model.write(System.out, "RDF/XML-ABBREV");
此writer, 也就是所謂的PrettyWriter, 利用RDF/XML縮寫語法把模型寫地更為緊湊. 它也能保存盡可能保留空白結點. 然而, 它并不合適來輸出大的模型. 因為它的性能不可能被人們所接受. 要輸出大的文件和保留空白結點, 可以用N-三元組的形式輸出:
// now write the model in XML form to a file
model.write(System.out, "N-TRIPLE");
這會產生類似于Tutorial3的輸出, 此輸出會遵循N-三元組的規格.
________________________________________
讀RDF
Tutorial 5 演示了如何將用RDF XML記錄的陳述讀入一個模型. 在此例中, 我們提供了一個小型RDF/XML形式的vcard的數據庫. 下面代碼會將其讀入和寫出. 注意: 如果要運行這個小程序, 應該把輸入文件放在你的classpath所指向的目錄或jar中.
// create an empty model
Model model = ModelFactory.createDefaultModel();
// use the class loader to find the input file
InputStream in = Tutorial05.class
?????????????????????????????? .getClassLoader()
?????????????????????????????? .getResourceAsStream(inputFileName);
if (in == null) {
??? throw new IllegalArgumentException(
???????????????????????????????? "File: " + inputFileName + " not found");
}
// read the RDF/XML file
model.read(new InputStreamReader(in), "");
// write it to standard out
model.write(System.out);
??????
read()方法中的第二個參數是一個URI, 它是被用來解決相對URI的. 因為在測試文件中沒有使用相對URI, 所以它允許被置為空值. 運行時, Tutorial5會產生類似如下的XML輸出
<rdf:RDF
? xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
? xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
>
? <rdf:Description rdf:nodeID="A0">
??? <vcard:Family>Smith</vcard:Family>
??? <vcard:Given>John</vcard:Given>
? </rdf:Description>
? <rdf:Description rdf:about='http://somewhere/JohnSmith/'>
??? <vcard:FN>John Smith</vcard:FN>
??? <vcard:N rdf:nodeID="A0"/>
? </rdf:Description>
? <rdf:Description rdf:about='http://somewhere/SarahJones/'>
??? <vcard:FN>Sarah Jones</vcard:FN>
??? <vcard:N rdf:nodeID="A1"/>
? </rdf:Description>
? <rdf:Description rdf:about='http://somewhere/MattJones/'>
??? <vcard:FN>Matt Jones</vcard:FN>
??? <vcard:N rdf:nodeID="A2"/>
? </rdf:Description>
? <rdf:Description rdf:nodeID="A3">
??? <vcard:Family>Smith</vcard:Family>
??? <vcard:Given>Rebecca</vcard:Given>
? </rdf:Description>
? <rdf:Description rdf:nodeID="A1">
??? <vcard:Family>Jones</vcard:Family>
??? <vcard:Given>Sarah</vcard:Given>
? </rdf:Description>
? <rdf:Description rdf:nodeID="A2">
??? <vcard:Family>Jones</vcard:Family>
??? <vcard:Given>Matthew</vcard:Given>
? </rdf:Description>
? <rdf:Description rdf:about='http://somewhere/RebeccaSmith/'>
??? <vcard:FN>Becky Smith</vcard:FN>
??? <vcard:N rdf:nodeID="A3"/>
? </rdf:Description>
</rdf:RDF>
________________________________________
Jena RDF 包
Jena是一個為語義網應用設計的一個Java API. 對應用開發者而言, 主要可用的RDF包是com.hp.hpl.jena.rdf.model. 因為API是以接口的方式定義的, 所以應用代碼可以使用不同的實現機制而不用改變代碼本身. 這個包包含了可以表示模型, 資源, 屬性, 文本, 陳述和其他RDF關鍵概念的接口, 還有一個用來創建模型的ModelFactory. 所以如果要應用代碼與實現類保持獨立, 最好盡可能地使用接口, 而不要使用特定的實現類.
com.hp.hpl.jena.Tutorial包包含了本教程所有例子中使用到的工作源代碼.
com.hp.hpl.jena.impl這些包包含了許多執行時所常用的執行類. 比如, 它們定義了諸如ResourseImpl, PropertyImpl和LiteralImpl的類, 這些類可以被不同的應用直接使用也可以被繼承使用. 應用程序應該盡可能少地直接使用這些類. 例如, 與其使用ResouceImpl來創建一個新的實例, 更好的辦法是使用任何正在使用的模型的createResource方法來完成. 那樣的話, 如果模型的執行采用了一個優化的Resouce執行, 那么在這兩種類型中不需要有任何的轉換工作.
________________________________________
操縱模型
到目前為止, 本教程主要講述的是如何創建, 讀入和輸出RDF模型. 現在是時候要講述如何訪問模型中的信息.
如果有了一個資源的URI, 那么就可以用Model.getResource(String uri)來從模型獲取這個資源對象. 這個方法被定義來返回一個資源對象, 如果它確實存在于模型中, 否則的話就創建一個新的. 例如, 如何從模型中獲取Adam Smith資源, 這個模型是Tutorial5中從文件讀入的:
// retrieve the John Smith vcard resource from the model
Resource vcard = model.getResource(johnSmithURI);
??
Resouce接口定義了一系列用于訪問某個資源的屬性的方法. Resource.getProperty(Property p)方法訪問了該資源的屬性. 這個方法不允許通常的Java訪問的轉換, 因為所返回的對象是Statement, 而不是你所預計的Property. 返回整個陳述的好處是允許應用程序通過使用它的某個訪問方法來訪問該陳述的客體來訪問這個屬性值. 例如如何獲取作為vcard:N屬性值的資源:
// retrieve the value of the N property
Resource name = (Resource) vcard.getProperty(VCARD.N)
??????????????????????????????? .getObject();
一般而言, 一個陳述的客體可以是一個資源或是一個文本. 所以此應用程序代碼知道這個值一定是個資源, 就將類型資源映射到返回的對象上. Jena的目標之一是提供會返回值為特定類型的方法, 這樣,應用程序就不必再做類型轉換工作, 也不必再編譯時做類型檢查工作. 以上的代碼片段也可以寫成更方便的形式:
// retrieve the value of the FN property
Resource name = vcard.getProperty(VCARD.N)
???????????????????? .getResource();
類似地, 屬性的文本值也可以被獲取:
// retrieve the given name property
String fullName = vcard.getProperty(VCARD.FN)
??????????????????????? .getString();
在這個例子中, 資源vcard只有一個vcard:FN屬性和一個vcard:N屬性. RDF允許資源有重復的屬性, 例如Adam可能有超過一個的昵稱. 讓我們假設他有兩個昵稱:
// add two nickname properties to vcard
vcard.addProperty(VCARD.NICKNAME, "Smithy")
.addProperty(VCARD.NICKNAME, "Adman");
正如前面所提到的那樣, Jena將RDF模型表示為一組陳述, 所以在模型中新增一個與原有陳述有著相同的主體,謂詞和客體的陳述并不會后什么作用. Jena沒有定義會返回模型中存在的兩個昵稱中的哪一個. Vcard.getProperty(VCARD.NICKNAME)調用的結果是不確定的. Jena會返回這些值中的某一個, 但是并不保證兩次連續的調用會同一個值.
一個屬性很有可能會出現多次, 而方法Resource.listProperty(Property p)可以用來返回一個iterator, 這個iterator會列出所有的值. 此方法所返回的iterator返回的對象的類型為Statement.我們可以像這樣列出所有的昵稱:
// set up the output
System.out.println("The nicknames of /""
????????????????????? + fullName + "/" are:");
// list the nicknames
StmtIterator iter = vcard.listProperties(VCARD.NICKNAME);
while (iter.hasNext()) {
??? System.out.println("??? " + iter.nextStatement()
??????????????????????????????????? .getObject()
??????????????????????????????????? .toString());
}
此代碼可以在Tutorial6中找到, 運行后會產生如下輸出:
The nicknames of "John Smith" are:
??? Smithy
??? Adman
一個資源的所有屬性可以用不帶參數的listStatement()方法列出.
________________________________________
查詢模型
前一節討論了如何通過一個有著已知URI的資源來操縱模型. 本節要討論查詢模型. 核心的Jena API只支持一些有限的查詢原語. 對于更強大查詢設備RDQL的介紹不在此文檔中.
列出模型所有陳述的Model.listStatements()方法也許是最原始的查詢模型方式. 然而并不推薦在大型的模型上使用這個方法. 類似的有Model.listSubjects(), 但其所返回的iterator會迭代所有含有屬性的資源, 例如是一些陳述的主體.
Model.listSubjectsWithProperty(Property p, RDFNode o)方法所返回的iterator跌代了所有具有屬性p且p屬性的值為o的資源. 我們可能會預計使用rdf:type屬性來搜索資源的類型屬性以獲得所有的vcard資源:
// retrieve all resource of type Vcard.
ResIterator iter = model.listSubjectsWithProperty(RDF.type, VCARD.Vcard);
然而, 不幸的是, 我們現在正在使用的vcard模式并沒有為vcard定義類型. 然而, 如果我們假設只有類型為vcard的資源才會使用vcard:FN屬性, 并且在我們的數據中, 所有此類資源都有這樣一個屬性, 那么我們就可以像這樣找到所有的vcard:
// list vcards
ResIterator iter = model.listSubjectsWithProperty(VCARD.FN);
while (iter.hasNext()) {
??? Resource r = iter.nextResource();
??? ...
}
所有的這些查詢方法不過是在原語查詢方法model.listStatements(Select s)上稍做變化而已. 此方法會返回一個iterator, 該iterator會跌代模型中所有被s選中的陳述. 這個selector接口被設計成可擴展的, 但是目前, 它只有一個執行類,那就是com.hp.hpl.jena.rdf.model包中的SimpleSelector類. 在Jena中使用SimpleSelector是很少見的情況, 即當需要直接使用一個特定類而不是使用接口. SimpleSelector的構造函數帶有三個參數:
Selector selector = new SimpleSelector(subject, predicate, object)
這個selector會選擇所有主體與參數subject相配, 謂詞與參數predicate相配, 且客體與參數object相配的陳述.?
如果某個參數值為null, 則它表示與任何值均匹配; 否則的話, 它們就會匹配一樣的資源或文本. (當兩個資源有相同的URI或是同一個空白結點時, 這兩個資源就是一樣的; 當兩個文本是一樣的當且僅當它們的所有成分都是一樣的.) 所以:
Selector selector = new SimpleSelector(null, null, null);
??
會選擇模型中所有的陳述.
Selector selector = new SimpleSelector(null, VCARD.FN, null);
??
會選擇所有謂詞為VCARD.FN的陳述, 而對主體和客體沒有要求. 作為一個特殊的縮寫,
listStatements( S, P, O )?
等同與
listStatements( new SimpleSelector( S, P, O ) )?
下面的代碼可以在Tutorial7中找到, 列舉了數據庫中所有vcard中的全名.
// select all the resources with a VCARD.FN property
ResIterator iter = model.listSubjectsWithProperty(VCARD.FN);
if (iter.hasNext()) {
??? System.out.println("The database contains vcards for:");
??? while (iter.hasNext()) {
??????? System.out.println("? " + iter.nextStatement()
????????????????????????????????????? .getProperty(VCARD.FN)
????????????????????????????????????? .getString());
??? }
} else {
??? System.out.println("No vcards were found in the database");
}
?????????
這個會產生類似如下的輸出:
The database contains vcards for:
? Sarah Jones
? John Smith
? Matt Jones
? Becky Smith
??
你的下一個練習是修改此代碼以使用SimpleSelector而不是使用listSubjectsWithProperty來達到相同的效果.
讓我們看看如何對所選擇的陳述實行更好的控制. SimpleSelector可以被繼承, 它的select方法可以被修改來實現更好的過濾:
// select all the resources with a VCARD.FN property
// whose value ends with "Smith"
StmtIterator iter = model.listStatements(
??? new SimpleSelector(null, VCARD.FN, (RDFNode) null) {
??????? public boolean selects(Statement s)
??????????? {return s.getString().endsWith("Smith");}
});
這個示例使用了一個簡潔的Java技術, 就是當創建此類的一個實例時重載一個內聯的方法. 這里selects(…)方法會檢查以保證全名以"Smith"做結尾. 重要的是要注意對主體, 謂語和客體的過濾是在調用selects(…)方法之前的執行的, 所以額外的測試只會被應用于匹配的陳述.
完整的代碼可以在Tutorial8中找到, 并會產生如下的輸出:
The database contains vcards for:
? John Smith
? Becky Smith
你也許會認為下面的代碼:
// do all filtering in the selects method
StmtIterator iter = model.listStatements(
? new
????? SimpleSelector(null, null, (RDFNode) null) {
????????? public boolean selects(Statement s) {
????????????? return (subject == null?? || s.getSubject().equals(subject))
????????????????? && (predicate == null || s.getPredicate().equals(predicate))
????????????????? && (object == null??? || s.getObject().equals(object))
????????? }
???? }
});
等同于:
StmtIterator iter =
? model.listStatements(new SimpleSelector(subject, predicate, object)
雖然在功能上它們可能是等同的, 但是第一種形式會列舉出模型中所有的陳述, 然后再對它們進行逐一的測試. 而第二種形式則允許執行時建立索引來提供性能. 你可以在一個大模型中自己試試驗證一下, 但是現在先為自己倒一杯咖啡休息一下吧.
________________________________________
對模型的操作
Jena提供了把模型當作一個集合整體來操縱的三種操作方法. 即三種常用的集合操作:并, 交和差.
兩個模型的并操作就是把表示兩個模型的陳述集的并操作. 這是RDF設計所支持的關鍵操作之一. 它此操作允許把分離的數據源合并到一起. 考慮下面兩個模型:
和?
當它們被合并時, 兩個http://...JohnSmith會合并成一個, 重復的vcard:FN箭頭會被丟棄, 此時就會產生:
讓我們看一下這個代碼的功能(完整的代碼在Tutorial9中), 再看看會發生什么.
// read the RDF/XML files
model1.read(new InputStreamReader(in1), "");
model2.read(new InputStreamReader(in2), "");
// merge the Models
Model model = model1.union(model2);
// print the Model as RDF/XML
model.write(system.out, "RDF/XML-ABBREV");
petty writer會產生如下的輸出:
<rdf:RDF
??? xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
??? xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
? <rdf:Description rdf:about="http://somewhere/JohnSmith/">
??? <vcard:EMAIL>
????? <vcard:internet>
??????? <rdf:value>John@somewhere.com</rdf:value>
????? </vcard:internet>
??? </vcard:EMAIL>
??? <vcard:N rdf:parseType="Resource">
????? <vcard:Given>John</vcard:Given>
????? <vcard:Family>Smith</vcard:Family>
??? </vcard:N>
??? <vcard:FN>John Smith</vcard:FN>
? </rdf:Description>
</rdf:RDF>
即便你不熟悉RDF/XML的語法細節, 你仍然可以清楚的看見模型如同預期般被合并了. 我們可以在類似的方式下運算模型的交操作和差操作.
________________________________________
容器
RDF定義了一類特殊的資源來表示事物的集合. 這些資源稱為容器. 一個容器的成員可以是資源也可以是文本. 有三類容器:?
一個BAG是一個無序的集合.
一個ALT是一個用來表示備選項的無序的集合.
一個SEQ是一個有序的集合.
一個容器由一個資源表示. 該資源會有一個rdf:type屬性, 屬性值為rdf:Bag, 或rdf:Alt, 或是rdf:Seq, 再或是這些類型的子類型, 這取決于容器的類型. 容器的第一個成員是容器的rdf:_1的屬性所對應的屬性值; 第二個成員是容器的rdf:_2屬性的值, 依此類推. 這些rdf:_nnn屬性被稱為序數屬性.
例如, 一個含有Smith的vcard的簡單bag容器的模型可能會看起來是這樣的:
雖然bag容器的成員們被rdf:_1,rdf_2等等的屬性所表示, 但是這些屬性的順序卻并不重要. 即便我們將rdf:_1和rdf:_2的屬性值交換, 但是交換后的模型仍然表示相同的信息.
Alt是設計用來表示被選項的. 例如, 我們假定有一個表示軟件產品的資源. 它可能有一個屬性指示從哪里可以獲得次軟件產品. 這個屬性值可能是一個包含各種下載地址的Alt集合. Alt是無序的, 除了rdf:_1屬性有著特殊的意義, 它表示默認的選項.
盡管我們可以用基本的資源和屬性機制來處理容器, Jena為處理容器設計了顯式的接口和執行類. 要避免在使用一個對象來操作容器的同時去使用低層方法來改變容器.
讓我們修改Tutorial8以創建一個bag:
// create a bag
Bag smiths = model.createBag();
// select all the resources with a VCARD.FN property
// whose value ends with "Smith"
StmtIterator iter = model.listStatements(
??? new SimpleSelector(null, VCARD.FN, (RDFNode) null) {
??????? public boolean selects(Statement s) {
??????????????? return s.getString().endsWith("Smith");
??????? }
??? });
// add the Smith's to the bag
while (iter.hasNext()) {
??? smiths.add(iter.next().getSubject());
}
如果我們將次模型輸出可以看見它含有類似如下的成分:
<rdf:RDF
? xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
? xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
>
...
? <rdf:Description rdf:nodeID="A3">
??? <rdf:type rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>
??? <rdf:_1 rdf:resource='http://somewhere/JohnSmith/'/>
??? <rdf:_2 rdf:resource='http://somewhere/RebeccaSmith/'/>
? </rdf:Description>
</rdf:RDF>
這表示了Bag資源.
容器接口提供了一個iterator來列舉容器的內容:
// print out the members of the bag
NodeIterator iter2 = smiths.iterator();
if (iter2.hasNext()) {
??? System.out.println("The bag contains:");
??? while (iter2.hasNext()) {
??????? System.out.println("? " +
??????????? (Resource) iter2.next())
??????????????????????????? .getProperty(VCARD.FN)
??????????????????????????? .getString());
??? }
} else {
??? System.out.println("The bag is empty");
}
并會產生如下的輸出:
The bag contains:
? John Smith
? Becky Smith
本例的可執行代碼可以在Tutorial10中找到.
Jena類所提供的操縱容器的方法包括增加新成員, 在容器中間插入新成員和刪除已有的成員. Jena容器類目前保證所使用的序數屬性列表會從rdf:_1開始并且是相鄰的. RDF核心工作小組放松了此項限制, 以允許有局部的容器表示. 所以這是Jena將來可能會修改的地方之一.
________________________________________
關于文本(Literals)和數據類型的更多探討
RDF文本(literals)并不僅僅是簡單的字符串而已. 文本可能有一個語言標簽來指示該文本使用的語言. 有英語語言標簽的文本"chat"會被認為與有著法語語言標簽的文本"chat"是不同的. 這個奇怪的特性是原有RDF/XML語法產生的贗象(artefact).
另外, 事實上共有兩種文本. 在一種里, 字符串成分只是簡單的字符串. 而在另一種里, 字符串成分被預計為格式良好的XML片段. 當一個RDF模型被寫成RDF/XML形式時, 一個特殊的使用parseType='Literal'的屬性(attribute)構造會被使用來表示它.
在Jena中, 當一個文本被創建時, 這些屬性就被設置了. 例如, 在Tutorial11中:
// create the resource
Resource r = model.createResource();
// add the property
r.addProperty(RDFS.label, model.createLiteral("chat", "en"))
.addProperty(RDFS.label, model.createLiteral("chat", "fr"))
.addProperty(RDFS.label, model.createLiteral("<em>chat</em>", true));
// write out the Model
model.write(system.out);
會產生:
<rdf:RDF
? xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
? xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'
>
? <rdf:Description rdf:nodeID="A0">
??? <rdfs:label xml:lang='en'>chat</rdfs:label>
??? <rdfs:label xml:lang='fr'>chat</rdfs:label>
??? <rdfs:label xml:lang='en' rdf:parseType='Literal'><em>chat</em></rdfs:label>
? </rdf:Description>
</rdf:RDF>
如果兩個文本被認為是相同的, 它們一定都是XML的文本或都是簡單的文本. 另外, 它們兩個要么都沒有語言標簽, 要么有相同的語言標簽. 對簡單的文本而言, 兩者的字符串一定是相同的. XML文本的等同有兩點要求. 第一, 前面提到的要求必須被滿足并且字符串必須相同. 第二, 如果它們字符串的cannonicalization一樣的話, 它們就是一樣的.(譯者注: 找不到cannonicalization中文的解釋.).
Jena接口也支持類型文字. 老式的對待類型文字的方法是把他們當成是字符串的縮寫: 有類型的值會通過Java的常用方法轉換為字符串, 并且這些字符串被存儲在模型中. 例如, 可以試一試(注意:對于簡單類型文字, 我們可以省略對model.createLiteral(…)的調用):
// create the resource
Resource r = model.createResource();
// add the property
r.addProperty(RDFS.label, "11")
.addProperty(RDFS.label, 11);
// write out the Model
model.write(system.out, "N-TRIPLE");
產生的輸出如下:
_:A... <http://www.w3.org/2000/01/rdf-schema#label> "11" .
因為兩個文本都是字符串"11", 所以只會有一個陳述被添加.
RDF核心工作小組定義了支持RDF數據類型的機制. Jena支持那些使用類型文字的機制; 但是本教程中不會對此討論.
________________________________________
術語表
空白結點?
表示一個資源, 但是并沒有指示該資源的URI. 空白結點的作用如同第一邏輯中的存在符合變量.
Dublin 核心
一個關于網絡資源的元數據標準. 更詳細的信息可以在Dublin Core web site找到.
文本
一個可以作為屬性值的字符串.
客體?
三元組的一部分, 也就是陳述的值.
謂詞
三元組的屬性部分.
屬性
屬性(property)是資源的一個屬性(attribute). 例如, DC.title是一個屬性, RDF.type也是一個屬性.
資源?
某個實體. 它可以是一個網絡資源, 例如一個網頁; 它也可以是一個具體的物理對象, 例如一棵樹或一輛車; 它也可以是一個抽象的概念, 例如國際象棋或足球. 資源由URI命名.
陳述
RDF模型中的一個箭頭, 通常被解理解為一個事實
主體?
RDF模型中箭頭出發點的那個資源.
三元組?
一個含有主體, 謂詞和客體的結構. 是陳述的另一種稱呼.
________________________________________
腳注
RDF資源的標簽可以包括一個片段標簽, 例如http://hostname/rdf/Tutorial/#ch-Introduction, 所以, 嚴格地講, 一個RDF資源是由一個URI表示的.
文本可以表示為一個字符串, 也可以有一個可選的語言編碼來表示該字符串的語言. 例如, 文本可能有一個表示英語的語言編碼"en", 而文本"deux"可能有一個表示法語的語言編碼"fr".
總結
以上是生活随笔為你收集整理的Jena文档《An Introduction to RDF and the Jena RDF API》的译文的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 巧妙设置yum软件库轻松解决软件包安装问
- 下一篇: 如何在Power 750上实现硬盘背板的