2014年5月4日 星期日

MongoDB 學習筆記之二 - MongoDB 簡介

一、什麼是 MongoDB?

MongoDB 是一套 open source 的 NoSQL DB。MongoDB 本身是用 C++ 開發的,它提供了高效能 (high performace)、高可用度 (high availability) 以及自動擴充 (automatic scaling) 等特色。

如下圖所示,在 MongoDB 中,一筆資料就是一個「document」(文件),所謂的 document 是由多個 field-value pairs 所組成的,其結構類似於 JSON 物件。一個 field (欄位) 裡的 value (值)可以是其他的文件、陣列或者由文件組成的陣列。


圖一、MongoDB 中每一筆紀錄都是 JSON 格式的 document

MongoDB 以 document 作為基本的資料儲存單位,有下列幾項好處
  1. JSON document 可對應於許多物件導向程式語言的原生資料類型 (如:物件),這也是近年來 JSONORM (Object-Relational Mapping) 日漸風行的原因 。
  2. RDBMS 的 JOIN 操作其實是成本高昂的,但如果採用 嵌入式文件 (embedded document) 或陣列的方式,可減少 JOIN 操作的使用。
  3. 動態的 schema 支援流暢的多型 (ploymorphism)。
本文寫作時的 MongoDB 社群正式版(production release)為 2.6.0 (2014/04/08 發行)。目前支援的 OS 如下表所示:

WindowsLinuxMac OS XSolaris
32-bit
64-bit

您可以在 MognoDB 官網的「Downloads」網頁下載。不過要注意的是,32 位元的版本有一個限制,即最多只能儲存 2 GB 的資料。所以,一般說來,你應該使用 64 位元版本。32 位元版本僅適用在 replica set 的「arbiter」(裁決者) 角色,或者用做 sharding 的 mongos (但請不要用於正式環境),相關資訊請參考這個網頁

MongoDB 的授權許可(license):
  • Database Server 和 Tools:AGPL
  • Drivers:Apache License
  • MongoDB Enterprise (企業版):MongoDB Inc. 提供商用的 licenses

MongoDB 的 key features(摘錄自官網):
  • High Performance
    • Support for embedded data models reduces I/O activity on database system.
    • Indexes support faster queries and can include keys from embedded documents and arrays.
  • High Availability (replica set)
    A replica set is a group of MongoDB servers that maintain the same data set, providing redundancy and increasing data availability.
    • automatic failover.
    • data redundancy.
  • Automatic Scaling (sharding)
    • Automatic sharding distributes data across a cluster of machines.
    • Replica sets can provide eventually-consistent reads for low-latency high throughput deployments.

二、關於 MongoDB Inc.

www.mongodb.org」是 MongoDB 社群版的官方網站,「www.mongodb.com」則是 MongoDB 企業版的官方網站,這兩個網站基本上都是由 Mongo Inc. 這家公司在維護。2013 年 8 月 27 日以前,MongoDB Inc. 其實叫做「10gen Inc.」的,為了避免造成大家的困惑,所以直接以產品名稱作為公司的名稱,詳情請參考 Tech Crunch 網站上這篇報導:《10gen Is Now MongoDB To Reflect Focus On NoSQL Database》。

2007 年,10gen Inc. 成立於紐約。由 Dwight Merriman (前 DoubleClick 創辦人兼 CTO )、Kevin P. Ryan (前 DoubleClick CEO、Gilt Groupe 創辦人)、 Eliot Horowitz (前 DoubleClck 工程師、 ShopWiki 創辦人兼 CTO ) 三人,接受了來自 Flybridge Capital Partners、In-Q-Tel、Intel、New Enterprise Associates (NEA)、Red Hat、紅杉資本 (Sequoia Capital) 以及 Union Square Ventures 共約 8,100 萬美金的投資。

10gen 最初的目標是希望能建立一個完全基於 open source 元件的 PaaS 架構,但因為該公司始終找不到一套既有的 DB 平台,可以符合他們建造此雲端架構的「原則」。因此,這家公司開始發展一套文件導向的資料庫系統 —— MongoDB

在發現這套系統的潛力之後,10gen 決定放棄原來的雲端平台計畫,全力投入 MongoDB 的開發與維護。2009 年,10gen 將 MongoDB 公開釋出為 open source 專案。2010 年 8 月,10gen 設立了位於美國西岸加州 Palo Alto 的辦公室。2011 年,陸續在維吉尼亞州的 Reston、英國倫敦、愛爾蘭都柏林、西班牙巴塞隆納、澳洲雪梨等地設立據點。

2012 年 9 月,在 華爾街日報 一篇《The Next Big Thing 2012》的報導裡,10gen 評為頂尖軟體公司排行榜的第 9 名

2013 年 4 月,10gen 搬進了舊紐約時報大樓。並於同年 8 月 27 日 10gen 宣佈將更名為 MongoDB Inc.,讓大家能由公司名稱直接就聯想到其代表性產品。

2013 年 10 月,MongoDB Inc. 在新一輪融資中獲得了1.5 億美元的風險創投資金,其估值達到 12 億美元,成為紐約估值最高的新創公司!(詳情請參考 Bloomberg 的這篇報導:《MongoDB Now King of NYC Startups With $1.2 Billion Valuation》和 TechOrange 的這篇報導:《NoSQL 資料庫的春天:MongoDB 獲得 1.5 億美元融資,它背後的故事是什麼?》)

三、MongoDB vs. RDBMS

以下這張 MongoDB 與 RDBMS 的對照表摘錄自官網的《SQL to MongoDB Mapping Chart》:

RDBMSMongoDB
databasedatabase
tablecollection
rowdocument or BSON document
columnfield
indexindex
table joinsembedded documents and linking
primary key
Specify any unique column or column combination as primary key.
primary key
In MongoDB, the primary key is automatically set to the _id field.
aggregation (e.g. group by)aggregation pipeline
See the SQL to Aggregation Mapping Chart.

下圖是在「users」這個 collection 裡新增一筆 document 的範例,也說明了 MongoDB 中的 Collection 與 Document 之間的關係:

圖二、Collection 與 Document 之間的關係

下表則是 MongoDB 與 RDBMS 在 DDL 與 DML 的比較表:

DML / DDLRDBMSMongoDB
DDLCREATE TABLEdb.collection.insert()
db.createCollection()
ALTER TABLEdb.collection.update()
DROP TABLEdb.collection.drop()
CREATE INDEXdb.collection.ensureIndex()
DROP INDEXdb.collection.dropIndex()
db.collection.dropIndexes()
DMLINSERTdb.collection.insert()
db.collection.save()
UPDATEdb.collection.update()
db.collection.save()
DELETEdb.collection.remove()
SELECTdb.collection.find()
db.collection.findOne()

※ 關於「db.collection.save()」,請參考官方文件
  •  如果未指定 _id,則等同執行「db.collection.insert()」。
  •  如果有指定 _id,則等同執行 upsert(即 db.collection.update({ _id: "XXXX"}, {document}, { upsert : true}))。若該 _id 已存在,則更新該筆 document;若該 _id 不存在,則新增一筆 document。

四、MongoDB 的安裝

MongoDB 的安裝非常簡單,官網已經提供了各種作業系統的安裝說明(如下),所以這裡就不再贅述囉。
五、結論

MongoDB 因為獲得相當龐大的資金挹注,所以銀彈充足,目前改版速度相當快。以最近兩次改版僅間隔三個月(2014/01/10 才出 2.4.9 版、2014/04/08 便跳到 2.6.0 版),以及 2.6.0 版的 release notes 所揭露的新功能與改版幅度來看,現在 MongoDB 應該處於急速成長的階段。

雖然如此,但我仍覺得 MongoDB 是相當值得一試的產品!

2014年5月2日 星期五

MongoDB 學習筆記之一 - 從 NoSQL 談起


近年來,資訊業界最夯的話題莫過於 Cloud Computing 和 Big Data 了,而「NoSQL」便是伴隨著這兩個主題所產生的技術主題。

MongoDB 也算是一種 NoSQL DB,所以我們會先從 NoSQL 說起,以利讀者們對於 NoSQL DB 有個基本認識以後,之後才來介紹 MongoDB。

一、NoSQL 的定義

一般人第一次看到「NoSQL」,直覺地都會以為是「不使用 SQL」的意思,其實不然,所以目前業界比較傾向使用「Not Only SQL」來解釋 NoSQL 一詞,也就是說通常是透過類似 SQL 的 API 來存取這類 DB。

以下則是摘錄《nosql-database.org》網站對「NoSQL」的定義:

「Next Generation Databases mostly addressing some of the points: being non-relationaldistributedopen-source and horizontally scalable. 」

圖一、《nosql-database.org》網站上對「NoSQL」的定義與特徵說明
二、NoSQL DB 的特徵

以下特徵採用的是《Toad World》網站「Survey distributed databases」一文的說法。
  1. Schema-less
    有類似「Table」的資料結構,但不需預先定義 schema。每一筆記錄的欄位數量與結構也可以不一樣。紀錄的內容與限制條件主要由應用程式來控管。
  2. Shared nothing architecture
    通常採用本地儲存、而非共同儲存設備(如 SAN 或 NAS)。本地磁碟的存取速度較透過網路傳輸快,也能透過增加節點的方式來擴充容量。使用一般規格的硬體即可(commodity hardware),故成本也隨之下降。
  3. Elasticity
    只需增加更多主機,便能立即擴充儲存容量與負載能力,所以不需要有停機時間(downtime)。當新節點加入後,資料庫便會開始分配任務給它。
  4. Sharding
    不將儲存視為龐大的空間,取而代之的是以「分片」(shard)方式來分割資料集。分片可在主機間進行複製(replication),但一個分片至少會由一部主機來管理。分片過大時,可採用自動分割方式,或者以程式為每一筆記錄指派所屬的分片ID。
  5. Asynchronous replication
    相較於 RAID (mirroring / stripping)或同步複製機制,NoSQL DB採用的是非同步的複製。這種方式較不會受到額外網路流量影響,所以能使寫入動作更快完成。又因為資料不會立即複製,所以某些時候可能發生資料遺失的狀況。此外,也沒有 lock 機制以保護某些特定資料。
  6. BASE instead of ACID
    由於 NoSQL DB 強調的是效能與可用度,所以「CAP Theorem」會比 RDBMS 的「ACID」更為重要。

三、NoSQL DB 的類型

世界頂尖的大師級人物  Martin Fowler、堪稱為「架構師中的架構師」。他在 2013 年的「goto; Conference」有一場主題為「Introduction to NoSQL」的演講,其中便對 NoSQL DB 做了一個分類:DocumentBig TableGraph 以及 Key-Value

而下面這張圖就是我仿效該場演講其中一張投影片所重製的:

圖二、NoSQL DB 的四大類型
以下則是放在 Youtube 上面的錄影,請大家一睹大師風采。


四、Non-Relational vs. Relational

2012 年 TechCrunch 網站上有一篇介紹 Big Data 五大開放源碼技術趨勢的文章 —— 「Big Data Right Now: Five Trendy Open Source Technologies」。該文作者 Tim Gasper 是 InfoChimps 的產品總監,InfoChimps 則是一家專門提供 Big Data 服務、平台的公司。

下圖也是我重製文中的一張照片,原圖是一張不是很清晰的照片。

圖三、Non-Relational vs. Relational

由上圖,我們可以知道:MongoDB 應該是一種 Non-RelationalNoSQLDocument based 的 DB:
  1. Non-Relational:代表了「無法或不使用 JOIN」。
  2. NoSQL:代表了「不使用標準 SQL 語言」。
  3. Document based:代表了「每筆紀錄都是一個 document」(相對於 RDBMS 的 row 而言)。
五、NoSQL DB 的理論基礎 - CAP & BASE

在 2000 年 的 PODC(Principles of Distributed Computing)會議上,柏克萊加州大學的 Eric Brewer 提出了著名的 「CAP Theorem」(CAP 定理)。2002 年,Seth Gilbert 和 Nancy Lynch 證明了這一理論:

  • Consistency(一致性)
    一致性是說資料的原子性,這種原子性在傳統 RDBMS 中是透過 Transaction 來保證的,當 Transaction 完成時,無論其是成功還是回滾,資料都會處於一致的狀態。在分散式環境中,一致性是說多個節點的資料是否一致。
  • Availability(可用性)
    可用性是說服務能一直保證是可用的狀態,當使用者發出一個請求,服務能在有限時間內返回結果。
  • Partition Tolerance(分區容錯性)
    Partition 是指網路的分區。可以這樣理解,一般來說,關鍵的資料和服務都會位於不同的 IDC 機房。
下圖擷取自 Eric Brewer 的「Towards Robust Distributed Systems」簡報:

圖四、CAP Theorem

CAP 定理告訴我們:「一個分散式系統不可能同時滿足上述這三個需求,三個要素中最多只能同時滿足兩點」。因此,架構設計師不要把精力浪費在設計如何能同時滿足三者的完美分散式系統上,而是應該進行權衡取捨。

但是對於分散式資料系統而言,「分區容錯性」(P) 是基本要求,否則就不能稱為「分散式系統」了。因此,一般我們所說的 NoSQL DB 都是在「一致性」(C) 和「可用性」(A) 之間尋求平衡(即上圖中以紅色虛線框所標示的交集處)。

BASE 是 Basically Available、Soft state、Eventually consistent 三個片語的簡寫,是對 CAP 中 CA 的延伸:
  • Basically Available:基本可用;
  • Soft-state:軟狀態/柔性交易,即狀態可以有一段時間的不同步;
  • Eventual consistency:最終一致性;
BASE 是基於 CAP 理論逐步演化而來,核心思想是:即便不能達到「強一致性」(Strong consistency),但可以根據應用特點採用適當的方式來達到「最終一致性」(Eventual consistency)的效果。

BASE 是反 ACID 的,它完全不同於 ACID 模型,犧牲強一致性,獲得基本可用性和柔性可靠性,並要求達到最終一致性。

CAP 和 BASE 理論是 NoSQL 的理論基礎。

六、NoSQL DB 的缺點

個人以為 NoSQL DB 有以下三個缺點:

  1. 不易轉換
    不像 RDBMS 有共通的標準語言 —— SQL,各種 NoSQL 都有自己的 API。所以一旦選定某種 NoSQL 產品,便不易再轉換至其他產品,比方說由 MongoDB 轉換為 Couchbase
  2. 不支援 ACID
    ACID 可說是「Transaction」的構成要件,也是所有 RDBMS 的主要特性。但大部分的 NoSQL DB 都不保證 ACID,必須使用一些變通技巧來實現(MongoDB 可使用 2 Phase Commits)。
  3. 不支援 JOIN
    因為 NoSQL DB 是 Non-relational 的,所以不支援 JOIN 操作。以 MongoDB 而言,除了一開始就要妥善規劃 Data Model 之外(如使用 embedded document 或 reference),也可搭配 Index 和  Aggregation(含 MapReduce)等技巧來提高查詢效能。

本文對於 NoSQL 就暫時介紹到這裡囉,下面「延伸閱讀」還有許多不錯的資訊,各位也可以參考一下喔!

※ 延伸閱讀:

  1. [酷壳] 分布式系统的事务处理:
    http://coolshell.cn/articles/10910.html
  2. [Toad World] Survey distributed databases
    http://www.toadworld.com/products/toad-for-cloud-databases/w/wiki/308.survey-distributed-databases.aspx
  3. [Martin Fowloer] NoSQL
    http://martinfowler.com/nosql.html
  4. [myNoSQL] NoSQL Databases and Polyglot Persistence: A Curated Guide
    http://nosql.mypopescu.com/

2014年2月8日 星期六

VMI 與 Design Patterns

在 GoF 的《Design Patterns》一書中共提到 23 種設計模式,有 18 種模式都使用了 VMI。所以 VMI 是瞭解 Design Patterns 的重要關鍵!

本文先從繼承、覆寫、重載、多型等基本概念談起,然後說明什麼是 VMI,最後再說明有哪些 Design Patterns 使用了 VMI。

一、繼承(Inheritance)
  1. 這是物件導向語言的基本特性與威力強大之處。類別之間如果有繼承關係,只需在父類別定義好 欄位(屬性)與 方法(行為),繼承該父類別的子類別就擁有同樣的欄位與方法。
  2. 此特性可大幅減少重複程式碼。但若使用不當,繼承亦會造成擴充困難與維護不易的問題。
二、覆寫(Overriding)與重載(Overloading)
  1. 繼承同一父類別之各項子類別,在部分欄位或方法難免有些許差異,透過這兩項技巧,既可保有繼承的優點,亦能增加子類別的擴充彈性。
  2. 覆寫與重載 (Overloading) 不同,覆寫是改變方法的內容,而重載是改變方法的回傳值與參數。但兩者均不會改變方法的名稱。(「方法名稱 + 參數」即所謂的「Signature」)
以下舉例說明:

鳥類(Bird):
public class Bird {
   String name;
   public Bird() {
      this.name = "Bird";
   }
   public void eat() {
      System.out.println(this.name + " can eat.");
   }
   public void fly() {
      System.out.println(this.name + " can fly.");
   }
}
烏鴉(Crow)
public class Crow extends Bird {
   public Crow() {
      this.name = "Crow";
   }
}
鴕鳥(Ostrich):
public class Ostrich extends Bird {
   public Ostrich() {
      this.name = "Ostrich";
   }
   Public void fly() {
      system.Out.Println(this.Name + " can't fly.");
   }
}
類別圖如下:


  • 繼承(Inheritance): 鳥類(Bird)是烏鴉(Crow)和鴕鳥(Ostrich)的父類別,是「繼承」的關係。
  • 覆寫(Overriding):但因為鴕鳥不會飛,所以我們「覆寫」其 fly() 的行為。

三、多型(Polymorphism)

對不同型別的物件提供單一介面。多型提供一些適用於多種型別的操作。多型又可細分為三種:
  • Ad hoc:又稱為「重載」,有 function overloading 或 operator overloading 兩種。
  • Parametric:又稱為「泛型」(generics 或 generic programming)。
  • Subtyping:即「子類別多型」,一個父類別參考可以指向不同子類別的物件。
以父類別類型 (Bird) 宣告的參考可以指向其下所有子類別物件 ( Crow, Ostrich… ) ,這種多型稱為「Subtyping Polymorphism」,又稱為「向上轉型」 (Upcasting)。

未使用多型使用多型
Crow myCrow = new Crow();
Ostrich myOstrich = new Ostrich();
Bird myCrow = new Crow();
Bird myOstrich = new Ostrich();
  • My crow "IS A" Crow.
  • My ostrich "IS AN" Ostrich.
  • My crow "IS A" Bird.
  • My ostrich "IS A" Bird.

有了多型,我們就可以使用同一個父類別參考來走訪所有子類別物件的實例欄位(instance field)。
Bird myBird = new Crow();
System.out.println(myBird.name);
myBird = new Ostrich();
System.out.println(myBird.name);
Crow
Ostrich
四、Virtual Method Invocation
  1. Virtual Method 又稱「Virtual Function」,當從父類別中繼承的時候,虛擬函式和被繼承的函式具有相同的簽名。但是在執行時期,執行系統將根據物件的型別,自動地選擇適當的具體實作執行。VMI 也適用於介面與實作。
  2. 虛擬函式是 OOP 實作多型的基本手段,更在 Design Patterns 之中扮演相當重要的角色。
  3. 在 Java 語言中,所有的方法預設都是虛擬函式。只有以關鍵字 final 標記的方法才是非虛擬函式。
  4. C++, Java 和 C# 都支援 VMI。 
VMI 是基於「多型」與「動態分派」(Dynamic Dispatch)的。所以,當您執行以下程式碼時,實際上將會呼叫到 Orstrich 類別的 fly()方法:

Bird myOstrich = new Ostrich();
myOstrich.fly();
 
Ostrich can’t fly.
如果 Java 不支援 VMI,則在編譯時期便會將 myOstrich.fly() 連結到  Bird  類別的 fly()方法,這是靜態分派。
Bird myOstrich = new Ostrich();
myOstrich.fly();
 Bird can fly.
有了 VMI,我們也可以這樣執行所有鳥類物件的 fly() 或 eat() 方法:

List Birds = new ArrayList();
Birds.add(new Bird());
Birds.add(new Ostrich());
Birds.add(new Crow());

for (Bird b : Birds) {
   b.fly();
}
其執行結果為:
Bird can fly.
Ostrich can't fly.
Crow can fly.
五、VMI 與 Design Patterns

在 GoF 的《Design Patterns》一書中共提到 23 種設計模式,有 18 種模式都使用了 VMI。所以 VMI 是瞭解 Design Patterns 的重要關鍵!

CreationalStructuralBehavioral
  1. Factory Method
  2. Abstract Factory
  3. Builder
  4. Prototype
  5. Singleton
  1. Adapter
  2. Bridge
  3. Composite
  4. Decorator
  5. Façade
  6. Flyweight
  7. Proxy
  1. Chain of Responsibility
  2. Command
  3. Interpreter
  4. Iterator
  5. Mediator
  6. Momento
  7. Observer
  8. State
  9. Strategy
  10. Template
  11. Visitor
s






參考資料:
  1. http://stackoverflow.com/questions/11431185/what-is-the-use-of-java-virtual-method-invocation
  2. http://en.wikipedia.org/wiki/Dynamic_dispatch
  3. http://zh.wikipedia.org/wiki/虚函数_(程序语言)
  4. http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokevirtual
  5. http://msdn.microsoft.com/en-us/library/aa645767(v=vs.71).aspx
  6. https://developer.apple.com/library/mac/documentation/cocoa/conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html

2014年1月4日 星期六

IDE v.s. 程式語言

如果你問 C# 開發者:「你使用哪一套 IDE?」,我想答案應該都是 Visual Studio;如果你問 Objective-C 的開發者同樣的問題,答案應該都是 Xcode。

可是,如果你問 Java 的開發者,那得到的答案有可能是 Eclipse、NetBeans、IntelliJ IDEA,甚至是 JBuilder...等等。

IDE 對程式語言的發展與社群具有決定性的影響。我甚至覺得像 .Net 或 Objective-C 這樣只有一種 IDE 可以選擇反而是比較好的,對於該程式語言的發展、普及、社群也比較會有正面幫助。 為什麼?我的答案是「一致」。

試想在一個 Java 開發團隊裡,如果沒有強制規定大家只能使用某種 IDE,不僅開發者之間無法共享許多使用 IDE 的知識、經驗、技巧之外, 開發工具的安裝設定組態以及 plugins 也都不能通用,如果成員要進行 pair programming 更是痛苦,導致整個團隊裡的每個人浪費了過多時間在熟悉、適應、比較甚至爭論各種 IDE 的好壞。

所以,有時候選擇太多也不一定是件好事....

2013年8月17日 星期六

Clean Code 讀後感想

重點如下:

1. Clean Code 追求的層次與境界比 Coding Rule 和 Coding Style 高,特別是程式碼的可讀性與易理解性,所以有講到程式碼的整體結構與佈局。

2. PG 的英文一定要很好,因為在這個指導原則下,寫 code 其實就好像是在用英文寫文章了(語境、語法、格式、段落、措辭、編排...等等都要考慮到)。

3. 要懂什麼是 S.O.L.I.D 五大原則,也要知道一些 Design Patterns(所以你應該也已經懂 UML 了)。

4. 也要懂 Refactoring、TDD、AOP...等等概念,所以建議搭配 Kent Beck 和 Martin Fowler 和其他一些大師的理念一起服用,效果會更佳。

5. 本書的舉例和引用都是 Java,所以比較適合 Java Programmer 閱讀,其他語言未必完全適用(除了像一些通則以外,如命名、註解、編排...等)。

6. 這本書至少要看過兩次以上,也要不停地練習、自我檢討才能內化為直覺反應。(也可能是我資質比較駑鈍吧,呵⋯)

以上都只是「專業知識技能」的部分,基本上還可以籍由後天的培訓來補足。這樣才可以說「慬」Clean Code。

但是真正要「做到」Clean Code,我覺得主要還是取決於個人的道德良知與自我嚴格要求,而這部分會比較偏人格特質的方面(沒錯,最好是有潔癖)。

若要 enforce 至公司內部 PG 團隊,則團隊裡的每個人都必須符合上述條件。否則,總是有人會一再地把 code 弄髒。此外,平時彼此之間要互相監督、提醒才行。

那如果要 enforce 至外包 PG 團隊呢?我覺得這就滿困難的了。因為在這種情況下,公司不太可能會對外包人員施以教育訓練,也無從了解其人格特質。

若公司派一個具備 Clean Code 能力的人全時間去督導呢?這不僅不符合成本效益,也是比較消極被動的控管方式。況且業界的驗收標準也尚未有所謂的 Clean Code 標準可茲依循遵守。

(外包:我不是只要功能、效能、資安都達到要求就好了嗎?怎麼現在還要要求我寫的 Code 是夠 Clean 呀!?)

2013年8月3日 星期六

將 DocBook 轉換成 PDF 格式

這是上一篇文章「DocBook 的 Line Numbering 和 Code Highlighting」的續集,今天我們要將 DocBook 輸出為 PDF 格式,但在這邊要先提醒一下:Apache FOP 似乎不認得 xslthl 轉出來的 tags,所以如果你想將 DocBook 輸出成 PDF,可能就要先放棄 Code HighLighting 的功能。小弟自己在嘗試過多次以後,已經宣告放棄了...XD

我本身使用的 OS 是 Windows 7 Pro 64bit,JVM 版本則是 Oracle JDK 1.6.0_43,建議您盡量採用和我一樣的環境與軟體版本,以免遭遇到其他問題。

接下來進入正題,開始說明轉換的步驟:

一、FOP 的取得、安裝及設定

請到「http://archive.apache.org/dist/xmlgraphics/fop/binaries/」下載 fop-1.0-bin.zip,因為我只試過這個版本,而且沒遇到什麼問題,所以建議您跟我採用一樣的版本吧!

上一篇文章一樣,也請解壓縮在「D:\docbook」下方,所以這個資料夾下方應該要長的像下面這樣才對:
  • docbook-xsl-1.78.1\
  • fop-1.0\
  • output\
  • saxon6-5-5\
  • tools\
  • xerces-2_11_0\
  • xslthl-2.1.0\
  • my_article.xml
  • my_article.xsl
  • my_code.java 
接下來,請修改「D:\docbook\fop-1.0\conf\fop.xconf」的內容,在「renders/render/fonts」區段加入如下內容( START 到 END 之間),讓  FOP 可以使用你系統內的字型(包含中文字型),這部分我是參考「Docbook开发手记」的:

<fop version="1.0">
    ...
  <renderers>
    <renderer mime="application/pdf">
        ...
      <fonts>
        ...
        
        <!-- START -->
         <!-- register all the fonts found in a directory and all of its sub directories (use with care) -->
         <directory recursive="true">file:///c:/windows/fonts/</directory>

         <!-- automatically detect operating system installed fonts -->
         <auto-detect/>
        <!-- END -->
      </fonts>

二、修改 my_article.xsl:

請將 my_article.xsl 替換成如下的內容:

<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:saxon="http://icl.com/saxon"
                extension-element-prefixes="saxon"
                version='1.0'>
 
<xsl:import href="file:///D:/docbook/docbook-xsl-1.78.1/fo/docbook.xsl"/>
 
<!--=====================================​=======================================
line numbering
====================================================​=========================-->
<xsl:param name="linenumbering.everyNth">1</xsl:param>
<xsl:param name="linenumbering.width">4</xsl:param>
<xsl:param name="linenumbering.separator"><xsl:text> </xsl:text></xsl:param>

<!--====================================================​========================
font setting
====================================================​=========================-->
<xsl:param name="title.font.family">Microsoft JhengHei</xsl:param>
<xsl:param name="body.font.family">Microsoft JhengHei</xsl:param>
<xsl:param name="monospace.font.family">MingLiU</xsl:param>
<xsl:param name="symbol.font.family">Cambria Math</xsl:param>

<!-- add bookmark and support more type of figure -->
<xsl:param name="fop1.extensions">1</xsl:param>

<!-- no indent for body content -->
<xsl:param name="body.start.indent">0pt</xsl:param>

</xsl:stylesheet>

三、使用以下指令進行轉換(在「D:\docbook」下面):

1. 先使用 Saxon 或 Xalan 來產生 fo 檔:

Saxon:
java -cp "D:\docbook\saxon6-5-5\saxon.jar;D:\docbook\docbook-xsl-1.78.1\extensions\saxon65.jar" com.icl.saxon.StyleSheet -o output\my_article.fo my_article.xml my_article.xsl use.extensions=1


Xalan:
java -cp "D:\docbook\tools\xalan.jar;D:\docbook\xerces-2_11_0\xml-apis.jar;D:\docbook\xerces-2_11_0\xercesImpl.jar;D:\docbook\docbook-xsl-1.78.1\extensions\xalan27.jar" org.apache.xalan.xslt.Process -OUT output\my_article.fo -IN my_article.xml -XSL my_article.xsl -L -PARAM use.extensions 1

產生的 fo 檔案位於「D:\docbook\output\my_article.fo」。

2. 再以 fop.bat 指令將 fo 檔轉成  PDF 檔:

fop-1.0\fop.bat -c d:\docbook\fop-1.0\conf\fop.xconf -fo output\my_article.fo -pdf my_article.pdf

產生的 PDF 檔案在「D:\docbook\my_article.pdf」。在執行的過程中,我是有看到一些錯誤訊息(請參考本文最下方),但是最終產生的 PDF 看起來是正常的,所以應該沒什麼關係。

我這邊轉出來的檔案就像下面這個樣子,希望你也成功了。


my_article.pdf



上一篇文章遭遇的問題一樣,Xalan 在轉換外部檔案my_code.java」時,即使有在 <textdata> 標籤加上「encoding="UTF-8"」屬性,中文(在註解的位置)仍會變成亂碼,但 Saxon 就不會。

如果您還想更深入瞭解 DocBook ,建議再參考以下這些網站:
總結來說,DocBook 確實是一套很不錯的文件格式標準(可以轉換成不同的文件格式),個人認為比較可惜的是:(1) 許多相關的文件與資料都已經很舊、過時了,(2) 相關的社群似乎也不再熱絡了。所以每次遇到問題時、要找答案或解決方式,都很困難...

附錄、執行 FOP 時看到的錯誤訊息

D:\docbook>fop-1.0\fop.bat -c d:\docbook\fop-1.0\conf\fop.xconf -fo output\my_ar
ticle.fo -pdf my_article.pdf
2013/8/3 下午 04:53:55 org.apache.fop.apps.FopFactoryConfigurator configure
資訊: Default page-height set to: 11in
2013/8/3 下午 04:53:55 org.apache.fop.apps.FopFactoryConfigurator configure
資訊: Default page-width set to: 8.26in
2013/8/3 下午 04:53:56 org.apache.fop.events.LoggingEventListener processEvent
警告: Font "Cambria Math,normal,700" not found. Substituting with "Cambria Math,
normal,400".
2013/8/3 下午 04:53:57 org.apache.fop.hyphenation.Hyphenator getHyphenationTree
嚴重的: Couldn't find hyphenation pattern zh_tw
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: This is a TrueType collection file with 3 fonts
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: Containing the following fonts:
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: MingLiU <-- selected
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: PMingLiU
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: MingLiU_HKSCS
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: This is a TrueType collection file with 3 fonts
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: Containing the following fonts:
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: MingLiU <-- selected
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: PMingLiU
2013/8/3 下午 04:53:57 org.apache.fop.fonts.truetype.TTFFile checkTTC
資訊: MingLiU_HKSCS

2013年7月31日 星期三

DocBook 的 Line Numbering 和 Code Highlighting

最近在製作 DocBook 文件時,遇到了「Line Numbering」和「Code Highlighting」問題,花了三天的時間終於解決,特別筆記在此紀錄一下解決的步驟。

首先,說一下前置作業:
  1. 取得「docbook-xsl」。從 SourceForge下載「docbook-xsl-1.?.?.zip」,請特別留意版本號!不要太新、也不要太舊,網址在:http://sourceforge.net/projects/docbook/files/docbook-xsl/ ,我自己下載的是 「docbook-xsl-1.78.1.zip」。
  2. 取得 XSLT 1.0 proccessor —— Saxon 或 Xalan,也一樣請留意版本號:
    1. Saxon:請從 http://saxon.sourceforge.net/#F6.5.5 下載,取得 Saxon 6.5.5 就好了,我自己下載的是「saxon6-5-5.zip」。
    2. Xalan:它被包在 Xerces2 Java 裡了,請從 http://xerces.apache.org/mirrors.cgi#binary
      下載 Binary 和 Tools,我自己下載的是「Xerces-J-bin.2.11.0.zip」和「Xerces-J-tools.2.11.0.zip」。
  3. 取得 XSLT Syntax Highlight  —— xslthl,請從 http://sourceforge.net/projects/xslthl/ 下載,我自己下載的是「xslthl-2.1.0-dist.zip」。
  4. 下載完上述檔案後,把它們解開在「D:\docbook」下方,也就是像這樣:
    • docbook-xsl-1.78.1\
    • saxon6-5-5\
    • tools\
    • xerces-2_11_0\
    • xslthl-2.1.0\
  5. 閱讀「DocBook XSL: The Complete Guide 4th Edition」的這兩篇:
    1. Chapter 27. Program listings - Line numbering
    2. Chapter 27. Program listings - Syntax highlighting
此外,要提醒的是:只有「Saxon」和「Xalan」才支援「Line Numbering」和「Code Highlighting」這兩項功能, xmltproc 並不支援喔!

接著,請準備好以下三個檔案(其內容列於本文底部),並盡量都使用一樣的編碼(如 UTF-8),也放到 D:\docbook  下面:
  1. my_article.xml:DocBook 文件。
  2. my_article.xsl:DocBook XSL 樣式檔。
  3. my_code.java:一段 Java 程式碼。
然後,在 D:\docbook 下執行以下指令,就可將 DocBook 格式轉換為 HTML 格式了:
  1. 如果你是用 Saxon 的話:
    java -cp "D:\docbook\xslthl-2.1.0\xslthl-2.1.0.jar;D:\docbook\saxon6-5-5\saxon.jar;D:\docbook\docbook-xsl-1.78.1\extensions\saxon65.jar" -Dxslthl.config="file:///D:/docbook/docbook-xsl-1.78.1/highlighting/xslthl-config.xml" com.icl.saxon.StyleSheet -o output\my_aritcle.html my_article.xml my_article.xsl use.extensions=1
    
  2. 如果你是用 Xalan 的話:
    java -cp "D:\docbook\xslthl-2.1.0\xslthl-2.1.0.jar;D:\docbook\tools\xalan.jar;D:\docbook\xerces-2_11_0\xml-apis.jar;D:\docbook\xerces-2_11_0\xercesImpl.jar;D:\docbook\docbook-xsl-1.78.1\extensions\xalan27.jar" -Dxslthl.config="file:///D:/docbook/docbook-xsl-1.78.1/highlighting/xslthl-config.xml" org.apache.xalan.xslt.Process -OUT output\my_article.html -IN my_article.xml -XSL my_article.xsl -L -PARAM use.extensions 1
    

轉換完的 my_article.html 內容就像下面這個樣子:



加上行號與程式碼 highlight 的 my_article.html

最後,我發現一個問題,就是 Xalan 在轉換外部檔案my_code.java」時,即使有在<textdata> 標籤加上「encoding="UTF-8"」屬性,中文(在註解的位置)會變成亂碼,但 Saxon 就不會。

至於其原因,我暫時沒時間去探究。因此,如果你也遇到同樣的問題,建議就先改用 Saxon 吧!(或者幫忙查一下原因,並回饋給小弟,:-p)

前面所提到的三個檔案內容如下:

1. my_article.xml
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">

<article lang="zh_tw">
  <title>快來測試 Line Numbering 和 Code Highlighting 的功能吧!</title>
  <sect1>
    <title>my_code.java</title>
      <programlisting linenumbering="numbered" startinglinenumber="1" language="java"><?dbhtml linenumbering.everyNth="1" linenumbering.separator=" " linenumbering.width="4"?><?dbfo linenumbering.everyNth="1" linenumbering.separator=" " linenumbering.width="4"?><textobject><textdata fileref="file:///D:/docbook/my_code.java" encoding="UTF-8" /></textobject></programlisting>        
  </sect1>
</article>
2.  my_article.xsl
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:saxon="http://icl.com/saxon"
                extension-element-prefixes="saxon" 
                version='1.0'>

<xsl:import href="file:///D:/docbook/docbook-xsl-1.78.1/html/docbook.xsl"/>
<xsl:import href="file:///D:/docbook/docbook-xsl-1.78.1/html/highlight.xsl"/>

<xsl:output method="html" 
            encoding="UTF-8"
            indent="yes"
            saxon:character-representation="native;decimal"/>
<!--=====================================​=======================================
Code Line Numbering & Highlighting Global Settings
====================================================​=========================-->
<xsl:param name="linenumbering.extension" select="1"></xsl:param>
<xsl:param name="linenumbering.everyNth">1</xsl:param>
<xsl:param name="linenumbering.width">4</xsl:param>
<xsl:param name="linenumbering.separator"><xsl:text> </xsl:text></xsl:param>
<xsl:param name="highlight.source" select="1"></xsl:param>
</xsl:stylesheet>
3. my_code.java
/**
 * 使用「Adapter」Design Pattern 
 */
abstract class HumanWorkerAdpater implements IWorker {
   public void work();
   public void eat();
}
abstract class RobotWorker implements IWorker {
   public void work();
}
IWoker HumanWorker = new HumanWorkerAdpater() {
   public work() {
      // working....
   }
   public eat() {
      // eating....
   }
}
IWoker RobotWorker = new RobotWorkerAdpater() {
   public work() {
      // working....
   }
}