天天看點

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont

<code>Realm</code>是一個移動端的資料庫,<code>Realm</code>是<code>SQLite</code>和<code>CoreData</code>的替代者。它可以節省你成千上萬行代碼和數周的工作,并且讓你精巧的制作出令人驚歎的使用者體驗。

文檔版本 0.93.2

需求:

iOS7以上 OS X10.9以上 WatchKit.

Xcode 6 及以上 必需.

支援Objective-C Swift 1.2 Swift 2.0

靜态庫安裝 (Objective-C &amp; Swift)

1.下載下傳最新的<code>Realm</code>發行版本并在本地解壓。

2.從 <code>ios/static/</code>目錄裡,把<code>Realm.framework</code>檔案拖動到你的Xcode開發項目裡的<code>File Navigator</code> 中。 確定<code>Copy items into destination group’s folder</code>已經被選中,按Finish。

3.在<code>Xcode file explorer</code>中選中你要的開發項目. 選擇<code>target</code>,點選<code>Build Phases</code>選項. 在<code>Link Binary with Libraries</code>裡按+,添加<code>libc++.dylib</code>。

4.如果使用<code>Realm + Swift</code>,拖動<code>Swift/RLMSupport.swift</code>到你的<code>Xcode project</code>的<code>File Navigator</code>中,點選<code>Copy items if needed</code>。

5.如果在OSX項目中使用Realm,點選左上角的 + ,選擇<code>New Copy Files Phase</code>,将其重命名為<code>Copy Frameworks</code>, 将Destination設定為Frameworks,并且添加<code>Realm.framework</code>。

動态庫安裝 (Objective-C &amp; Swift)

2.來到Xcode工程的<code>General</code>設定界面,從 <code>ios/dynamic/</code>或者<code>osx/</code> 目錄裡,把<code>Realm.framework</code>檔案拖動到<code>Embedded Binaries</code>裡面。 確定<code>Copy items into destination group’s folder</code>已經被選中,按Finish。

3.In your unit test target’s “Build Settings”, add the parent path to Realm.framework in the “Framework Search Paths” section.

4.If using Realm with Swift, drag the file at Swift/RLMSupport.swift into the File Navigator of your Xcode project, checking the Copy items if needed checkbox.

5.If using Realm in an iOS 8 project, create a new “Run Script Phase” in your app’s target’s “Build Phases” and paste the following snippet in the script text field: bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh" This step is required to work around an App Store submission bug when archiving universal binaries.

通過CocoaPods安裝 (Objective-C Only)

如果你使用<code>CocoaPods…</code>

1.把<code>pod "Realm"</code>添加到你的Podfile中。

2.在指令行中執行<code>pod install</code>。

3.将<code>CocoaPods</code>生成的<code>.xcworkspace</code>運用到你的開發項目中即可。

你也可以自行手動安裝:打開<code>release zip</code>中的<code>plugin/RealmPlugin.xcodeproj</code>, 點選編譯<code>build</code>。 重新開機Xcode生效。

如果你使用Xcode建立檔案<code>File &gt; New &gt; File… — or ⌘ N</code>,可以看到有一個建立Realm模型<code>create a new realm model</code>的選項。

我們另外提供了一個獨立的資料庫管理工具,用來檢視和編輯realm資料庫<code>.realm</code>。

你可以從<code>browser/</code>的<code>release zip</code>目錄下找到它。

使用菜單中的工具<code>tool</code>&gt;生成示範資料庫<code>generate demo database</code>, 你可以生成一個測試資料庫(當然裡面的資料是樣本資料).

你可以在<code>examples/</code>路徑裡面找到一個檔案,叫做<code>release zip</code>。 裡面包含了<code>Objective-C</code>,<code>Swift</code>和<code>RubyMotion</code>的示例程式。 它們示範了<code>Realm</code>得很多功能和特性,例如資料庫遷移,如何與<code>UITableViewController’s</code>一起使用,加密等等。

Realm的資料模型是用傳統的Objective-C 接口<code>interface</code>和<code>@properties</code>定義的。 就隻要定義<code>RLMObject</code>的一個子類<code>subclass</code>或者一個現成的模型類,你就能輕松建立一個<code>Realm</code>的資料模型對象<code>data model object</code>。<code>Realm</code>模型對象和其他的<code>objective-c</code>的功能很相似–你可以給它們添加你自己的方法<code>method</code>和協定<code>protocol</code>然後和其他的對象一樣使用。

唯一的限制就是從它們被建立開始,隻能在一個線程中被使用。

如果你已經安裝了我們的<code>Xcode</code>插件 那麼,在<code>New File</code>對話框中會有一個很漂亮的樣闆,你可以用它來建立<code>interface</code>和<code>implementation</code>檔案。

你隻需要為對象清單添加目标類型的屬性<code>property</code>或者<code>RLMArray</code>的,就可以建立資料庫關聯和嵌套資料結構

<code></code>

RLMObject的相關細節.

屬性<code>property</code>種類

Realm支援以下的屬性<code>property</code>種類: <code>BOOL</code>,<code>bool</code>, <code>int</code>,<code>NSInteger</code>,<code>long</code>,<code>float</code>,<code>double</code>,<code>CGFloat</code>,<code>NSString</code>,<code>NSDate</code> 和<code>NSData</code>。

你可以使用<code>RLMArray&lt;NSObject&gt;</code> 和<code>RLMObject</code>來模拟對一或對多的關系

<code>Realm</code>也支援<code>RLMObject</code>繼承。

屬性<code>property</code>特性

請注意<code>Realm</code>忽略了<code>objective-c</code>的<code>property attributes</code>,像<code>nonatomic</code>,<code>atomic</code>,<code>strong</code>,<code>copy</code>,<code>weak</code>等等。

是以,為了避免誤解,我們推薦你在寫入模型的時候不要使用任何的<code>property attributes</code>。但是,假如你設定了,這些<code>attributes</code>會一直生效直到<code>RLMObject</code>被寫入<code>realm</code>資料庫。

無論<code>RLMObject</code>在或不在<code>realm</code>中,你為<code>getter</code>和<code>setter</code>自定義的名字都能正常工作。

資料模型定制

幾個存在的類方法進一步指定模型資訊:

<code>+attributesForProperty:</code>可以被重寫來來提供特定屬性<code>property</code>的屬性值<code>attrbutes</code>例如某個屬性值要添加索引。

<code>+defaultPropertyValues</code>可以被重寫,用以為建立的對象提供預設值。

<code>+primaryKey</code>可以被重寫來設定模型的主鍵。定義主鍵可以提高效率并且確定唯一性。

<code>ignoredProperties</code>可以被重寫來防止<code>Realm</code>存儲模型屬性。

對對象的所有更改(添加,修改 和删除)都必須通過寫入事務完成。

<code>Realm</code>的對象可以被執行個體化并且被單獨使用,和其他正常對象無異。

如果你想要在多個線程中共享或者永久儲存以重複使用對象,你必須将其存儲到Realm資料庫中——這個操作必須在寫事務中完成。 你可以參照如下代碼添加一個對象:

建立一個對象

<code>Person *author = [[Person alloc] init];author.name = @"David Foster Wallace";</code>

擷取一個預設realm對象

<code>RealmRLMRealm *realm = [RLMRealm defaultRealm];</code>

你隻須這麼做一次(單線程操作) Add to Realm with transaction

等到你把這個對象添加到realm資料庫裡面之後, 你可以在多個線程裡面共享之。

并且從現在開始,你所做的每一次更改(必須在一個寫事務中完成)也會被永久儲存。

等到寫事務完成,這個更改将對所有共享這個Realm資料庫的線程可見。

需要注意的是,寫入操作會互相阻塞,而且其相對應的程序也會受到影響。

這和其他的永久資料存儲解決方案是一樣的,是以我們建議你使用常用的,也是最有效的方案, 将所有寫入放到一個單獨的程序中。

還要注意的是,因為realm的MVCC結構, 讀取并不會 因為一個進行中的寫事務而受到影響。

所有的資料抓取都很簡單,并且直到獲得資料之後才建立副本。

關于使用RLMResults的小貼士:

Realm的對象查詢傳回一個RLMResults對象。它包含了一系列的RLMObject。

RLMResults有一個與NSArray很相似的interface(接口)并且對象可以通過索引(index)下标擷取。

但不同于NSArrays的是,RLMResult是歸類的——它隻能容納一種RLMObjects類型。

根據種類擷取對象從realm中擷取對象的最基本方法就是 [RLMObject allObjects], 它傳回一個RLMResults,裡面是查詢的子類的所有RLMObject執行個體。

謂詞/條件查詢

如果你對 NSPredicate很熟悉的話, 那麼你就已經知道怎麼在realm裡面查詢了。

<code>RLMObjects</code>, <code>RLMRealm</code>, <code>RLMArray</code>和<code>RLMResults</code>都提供很好的<code>methods</code>來查詢特定的<code>RLMObjects</code>:

你隻需要傳遞相應地<code>NSPredicate</code>執行個體,謂詞字元串,謂詞格式字元串,就可以擷取你想要的<code>RLMObjects</code>執行個體。就和<code>NSObject</code>一樣的。

舉個例子,下面的代碼就是對上面的拓展。 通過調用<code>[RLMObject objectsWhere:]</code>,獲得了預設realm資料庫中的所有顔色是黃褐色的,名字開頭是“B”的狗的執行個體。

<code>// 條件查詢 RLMResults *tanDogs = [Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]; // 使用一個NSPredicate對象查詢 NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@", @"tan", @"B"]; tanDogs = [Dog objectsWithPredicate:pred];</code>

可以參看Apple的Predicates Programming Guide 了解更多關于如何建立謂詞。

Realm支援很多常見的謂詞:在比較中, 操作數可以是屬性名或者常量。但是其中至少有一個是屬性名。

隻有int, long, float, double, and NSDate這些屬性類型(property types)支援 ==, &lt;=, &lt;, &gt;=, &gt;, !=, 和 BETWEEN這些比較操作符。布爾屬性可以支援==和!=。

在NSString和NSData屬性中, 我們支援的操作符有 ==, !=, BEGINSWITH, CONTAINS和ENDSWITH。

realm還支援如下的複合型操作符: AND, OR, NOT注意,我們雖然不支援aggregate expression type,但是我們支援BETWEEN操作符, 例如:RLMResults *results = [Person objectsWhere:@"age BETWEEN %@", @[42, 43]];詳詢[RLMObject objectsWhere:].

條件排序

在很多情況下,我們都希望擷取或者查詢傳回的結果都能按照一定條件排序。

是以,RLMArray支援使用指定的屬性對資料列進行排序。

Realm允許你指定一個排序要求并且根據一個或多個屬性進行排序。

舉例來說, 下面代碼呼叫了[RLMObject objectsWhere:where:]對傳回的資料”dogs”進行排序,排序的條件是名字的字母表升序。:

了解更多:[RLMObject objectsWhere:]和[RLMResults sortedResultsUsingProperty:ascending:]。鍊式查詢

Realm查詢引擎的一個獨特屬性就是它能夠進行簡單快捷的鍊式查詢, 而不需要像傳統資料庫一樣的麻煩。舉個例子來說,假如你要所有黃褐色的小狗的結果序列,然後從中查找名字開頭是“B“的小狗。 你可以發送如下的請求。

預設的realm資料庫

你可能已經注意到了我們總是通過[RLMRealm defaultRealm]來獲得realm資料庫。

這個method傳回一個RLMRealm對象,指向一個叫做”default.realm“的檔案。

這個檔案在你的app的Documents檔案夾裡面。

其他的realm資料庫

有的時候擁有多個分離的資料庫是非常有必要的。

例如,如果你需要綁定資料到一個App,你可以打開另一個隻讀的Realm資料庫,參考See [RLMRealm realmWithPath:] 和 [RLMRealm realmWithPath:readOnly:error:]

請注意傳遞到[RLMRealm realmWithPath:]的路徑必須是有寫入權限的。

存儲可寫Realm資料庫的最常用路徑就是iOS的“Documents”和OSX的“Application Support”。

跨線程使用資料庫

在不同的線程間使用同一個Realm資料庫,你必須呼叫[RLMRealm defaultRealm], [RLMRealm realmWithPath:] 或者 [RLMRealm realmWithPath:readOnly:error:]以得到個線程相對應的realm對象。

隻要你路徑一緻,所有的RLMRealm對象指向的檔案就一緻。

我們還不支援跨線程共享RLMRealm對象。

純記憶體資料庫

正常的Realm資料庫是存儲在硬碟上的, 但你也可以通過使用

[RLMRealm inMemoryRealmWithIdentifier:]來建立一個純記憶體資料庫。

純記憶體資料庫在每次程式退出時不會儲存資料。

但是,這不會妨礙到realm的其他功能,包括請求,關系和線程安全。

假如你需要靈活的資料讀寫但又不想永久儲存,那麼純記憶體資料庫對你來說一定大有裨益。

注意: 如果某個純記憶體Realm執行個體沒有被引用,所有的資料就會被釋放。

強烈建議你在app中用強引用來鉗制所有建立的純記憶體Realm資料庫。

可以通過使用RLMObject 和 RLMArrayproperties互相聯結。

假如說你預先定義了一個”人“模型(see above),我們再來建立一個”狗“模型。:

對一

對于多對一和一對一關系, 僅僅隻需要定義一個RLMObject子類類型的property:

你可以像往常一樣使用之:

當你使用RLMObject properties,你可以通過正常的property文法擷取嵌套屬性。

例如rex.owner.address.country會周遊這個對象圖來獲得你所想要的子類。對多

你可以通過使用RLMArrayproperties來定義一個對多關系。

RLMArrays包含多個同類的RLMObjects,并且擁有一個和NSMutableArray非常相似的interface(接口)。

如果想要把”dogs“(多個”Dog“)屬相添加到”Person“模型中來,我們必須定義一個RLMArray ————這可以通過在相應的模型(model)interface中添加一個宏(macro)來實作。

然後你可以定義屬性,類型為RLMArray。

我們又可以像往常一樣讀取和指派啦。

每當一次寫事務完成Realm執行個體都會向其他線程上的執行個體發出通知,可以通過注冊一個block來響應通知:

隻要有任何的引用指向這個傳回的notification token,它就會保持激活狀态。

在這個注冊更新的類裡,你需要有一個強引用來鉗制這個token, 因為一旦notification token被釋放,通知也會自動解除注冊。

具體内容:[Realm addNotificationBlock:] 和 [Realm removeNotificationBlock:]。

通過把大量的寫入放入到一個大型事務中,Realm可以大大的提高大型資料讀取的運作效率。

事務可以在背景通過GCD運作,這樣可以避免阻塞主程序。

RLMRealm并不是線程安全的,是以你必須在每一個你需要讀寫的程序或者排程隊列中添加RLMRealm執行個體。

這裡有一個在背景隊列中添加百萬級資料的例子。

可以參考RLMRealm。

Realm輕松的整合了REST API, 這使得Realm在幾個方面勝過了無本地緩存的REST API:

Realm緩存資料使得你能提供離線體驗,普通的REST API無法做到這一點——他們一定需要網絡連接配接。

通過在本地存儲你的整個資料集,你可以在本地進行查詢,這能提供比普通REST API好很多的本地搜尋體驗。

可直接從Realm查詢資料,不必等待伺服器端複雜的API處理。

減輕伺服器端負荷,隻需要在更新和修改資料時進行必要的通路。最佳操作

異步請求

— 網絡請求和其他一些操作應該放到背景,以免影響互動效果。同理Realm資料庫中大規模插入和修改應該在背景進行。你可以用通知來相應背景操作。

緩存資料庫大于使用者當下查詢

— 我們建議你對可能使用的資料進行預處理并且存儲到Realm中。 這麼做可以讓你在本地資料集中進行查詢。

插入或更新

— 如果你的資料集有一個特有的辨別符, 例如一個主鍵, 你可以用它來判定插入還是更新。隻需要使用[RLMObject createOrUpdateInDefaultRealmWithObject:]:如果你從API得到響應, 這個method會從Realm中查詢這個響應是否有記錄。

如果在本地有記錄, 就可以從響應中根據最新的資料進行更新。如果沒有,就将該響應插入到Realm資料庫中。

示例

以下是一個如何應用一個使用了REST API的Realm的示例。在這個示例裡,我們将從foursquare API裡擷取一組JSON格式的資料,然後将它以Realm Objects的形式儲存到預設realm資料庫裡。 如果你想參考類似示例的實際操作,請觀看 video demo.

首先我們要建立一個預設Realm資料庫的執行個體,用于存儲資料以及從 API 擷取資料。為了更簡單易讀,我們在這個例子裡面運動了 [NSData initWithContentsOfURL].

這條響應包含了JSON數組,形式類似于:

要想把JSON資料導入Realm中我們有很多辦法,殊途同歸。你可以讀取 NSDictionary然後将其屬性通過插入功能手動映射到一個RLMObject上。

為了示範效果,在這個示例裡,我們将直接把 NSDictionary插入到Realm中,然後讓Realm自動快速将其屬性映射到RLMObject上。

為了確定示例能夠成功,我們需要一個屬性完全比對JSON資料特點的RLMObject的架構。JSON資料特點如果得不到比對,将在植入時自動被忽略。

以下RLMObject的定義是有效的:

因為結果集是以數組的形式給我們的,我們要呼叫 [Venue createInDefaultRealmWithObject:] 來為每個元素建立一個對象. 這裡會建立 Venue 和一個JSON格式的子對象,并将這些建立的對象加入到預設realm資料庫中:

資料庫遷移

當你和資料庫打交道的時候,時不時的你需要改變資料模型(model),但因為Realm中得資料模型被定義為标準的Objective-C interfaces,要改變模型,就像改變其他Objective-C interface一樣輕而易舉。舉個例子,假如我們有如下的interface, 叫“Person.h”:

我們想要更新資料模型,因為我們要添加一個“全名”(fullname)屬性, 而不是用分開的“姓”+“名”。要達到這樣的目的,我們隻需要改變對象的interface,如下:

在這個時候如果你儲存了資料,那麼Realm就會注意到代碼和硬碟資料不比對。 每當這時,你必須對資料構架進行遷移,否則就會有錯誤抛出。

進行遷移

你可以通過呼叫[RLMRealm setSchemaVersion:withMigrationBlock:]自定義資料遷移以及相應的構架版本。你的資料遷移子產品将會為你提供相應地邏輯,用來更新資料構架。呼叫[RLMRealm setSchemaVersion:withMigrationBlock:]之後, 任何需要遷移的Realm資料庫都會自動使用指定的遷移子產品并且更新到相應地版本。

例如,假設我們想要把之前‘Person’的子類遷移,如下所示是最簡化的資料遷移組:

我們所需要做的就是用一個空子產品更新版本,表明這個構架已經被Realm自動更新了。

雖然這是系統能接受的最簡化的遷移,我們應當用有意義的代碼來填充這些新的屬性(這裡就是“fullname”)。在資料遷移子產品中,我們可以呼叫[RLMMigration enumerateObjects:block:] 來列舉某種格式的每一個Realm檔案,并執行必要的遷移判定:

一旦遷移成功結束,Realm和其所有檔案即可被你的app正常存取。

添加更多的版本

假如說現在我們有兩個之前版本的Person類:

我們的遷移子產品裡面的邏輯大緻如下:

想要了解更多關于資料庫的架構遷移, 參見migration sample app.

Linear Migrations

假如說,我們的app有兩個使用者: JP和Tim. JP經常更新app,但Tim卻經常跳過。 是以JP可能下載下傳過這個app的每一個版本,并且一步一步的跟着更新構架:他下載下傳第一次更新,從v0到v1, 第二次更新從v1到v2,以此類推,井然有序。相反,Tim很有可能直接從v0跳到了v2。 是以,你應該使用非嵌套的 if (oldSchemaVersion &lt; X)結構來構造你的資料庫遷移子產品,以確定不管他們是在使用哪個版本的構架,都能看見所有的更新。

當你的使用者不按規則出牌,跳過有些更新版本的時候,另一種情況也會發生。 假如你在v2裡删掉了一個“email”屬性,然後在v3裡又把它重新引進了。假如有個使用者從v1直接跳到v3,那Realm不會自動檢測到v2的這個删除操作因為存儲的資料構架和代碼中的構架吻合。這會導緻Tim的Person對象有一個v3的email property,但裡面的内容卻是v1的。這個看起來沒什麼大問題,但是假如兩者的内部存儲類型不同(比如說: 從ISO email representation 變成了自定義),那麻煩就大了。為了避免這種不必要的麻煩,我們推薦你在if (oldSchemaVersion &lt; 3)中,nil out所有的email property。

下一步

你可以看一下我們的我們給出的示例。看看在app中應該如何使用realm(我們已經有越來越多的樣本了!)

做一個愉快地碼農!你也總是可以在realm-cocoa上實時的和其他開發者聊天。

realm現在還是beta版本。我們還在為1.0的釋出一直不斷的添加新特性,修複bug。我們整理了一些普遍存在的限制

Realm CocoaPods 不支援Swift 項目開發

CocoaPods 暫時還不支援Swift 項目開發(戳這個GitHub issue #2222). 想要在一個Swift 項目中使用Realm,請參見上面的步驟.

暫時不支援通知細節

雖然要在realm發生變化的時候可以接到通知 (參見 通知), 但現在我們還不能從notification裡面得知什麼東西被添加/删減/移動/更新了。 我們會盡快完善這個功能的。

NSDate在秒的地方被截斷

一個包含非整秒數的NSDate在存入realm的時候,會在秒的地方被截斷。我們正在修複這個問題。 可參考 GitHub issue #875。同時,你可以無損存儲NSTimeInterval格式。

Realm對象的Setters &amp; Getters不能被重寫

因為Realm重寫了setters和getters, 是以你不可以在你的對象上再重寫。一個簡單的替代方法就是:建立一個新的realm-ignored屬性(它的accessors可以被重寫, 并且可以呼叫其他的getter和setter)。

不支援 KVO

Realm不支援KVO, 但它有自己的通知機制(see 通知).

Realm檔案不能被兩個程序同時通路

盡管Realm檔案可以在多個線程中被同時通路, 它們每次隻能被一個程序通路。這對iOS 8和OSX應用有影響。不同的程序應該複制或者建立Realm檔案。 敬請期待多程序支援。

realm的支援庫有多大?

一旦你的app編譯完成, realm的支援庫應該隻有1 MB左右。 我們釋出的那個可能有點大(iOS ~37MB, OSX ~2.4MB), 那是因為它們還包含了對其他構架的支援(ARM, ARM64,模拟器的是X86)和一些編譯符号。 這些都會在你編譯app的時候被Xcode自動清理掉。

我應該在正式産品中使用realm嗎?

自2012年起, realm就已經開始被用于正式的商業産品中了。

正如你預期,我們的objective-c &amp; Swift API 會随着社群的回報不斷的完善和進化。 是以,你也應該期待realm帶給你更多的新特性和版本修複。

我該如何保護Realm裡面的資料?

Realm 有幾種加密方法, 各有千秋。參考這個Github評論。Realm将在未來支援跨平台加密。

我要付realm的使用費用嗎?

不要, Realm的徹底免費的, 哪怕你用于商業軟體。

你們計劃怎麼賺錢?

其實,我們靠着我們的技術,已經開始賺錢啦!這些錢來自于我們銷售企業級産品的利潤。如果你想要得到比普通發行版本或者realm-cocoa更多的支援, 我們很高興和你發郵件聊聊。 我們一直緻力于開發開源的(Apache 2.0),免費的realm-cocoa。

我看到你們在代碼裡有“tightdb”或者“core”, 那是個啥?

TightDB是我們的C++存儲引擎的舊名。core 現在還沒有開源但我們的确想這樣做(依舊使用Apache2.0)假如我們有時間來進行清理,重命名等工作。同時,它的二進制發行版在Realm Core(tightDB)Binary License裡面可以找到。

訂閱我們定期釋出的community newsletter。

裡面有一些非常有用的提示和其他的用例。當有新的Realm部落格或者教程出現,郵件也會通知你。StackOverflow: 查找之前的有#realm标簽的問答, — 或者,開一個新的。

推特: 聯系@realm 或者用#realm标簽。

Email: docs/cocoa/latest [email protected].

<a href="https://realm.io"></a>

&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt; &lt;html&gt;&lt;head&gt;&lt;meta http-equiv="Cont