RTF文檔格式是微軟提出的一種用於描述帶格式文本的文檔格式,上個世紀就提出來了,一直用到如今,並且不少程序都支持這種格式,微軟的Office軟件家族,Windows寫字板軟件等等都支持,並且Windows操做系統的剪切板和OLE拖拽操做也支持RTF文檔,這樣就容許不一樣的軟件經過RTF格式相互交流帶格式文本。好比我用的VS.NET2003中的C#代碼編輯器,在其中複製了一段代碼文本,在MS Word中粘貼所得就是具備高亮度顯示的文本。所以RTF格式的做用仍是不小的,並且RTF格式是純文本格式,不是二進制格式,讀寫都不算難。html
RTF文檔格式和HTML,XML之類的標記語言有點相似,原理不復雜,但內容仍是比較多的,在微軟的MSND中就有文章詳細的介紹了RTF格式,地址是 ms-help://MS.MSDNQTR.2003FEB.2052/dnrtfspec/html/rtfspec.htm ,你用記事本打開一個RTF文檔,能夠發現其中也是純文本數據,並且通常全是ANSI字符,RTF文檔通常採用ASNI字符編碼格式進行存儲,其中是不能直接保存漢字等編碼大於127的字符,要保存得使用轉義字符。RTF文檔中使用一對花括號"{ }"來定義一個組,組能夠套嵌定義;用"/"來開始定義一個指令和轉義字符;此外還能包含純文本數據。全部的指令和轉義字符都必須包含在一個組中,一個RTF文檔只有一個根組,這點有點相似XML文檔只能有一個根節點的規定。數據庫
咱們使用Windows寫字板新建一個RTF文檔,只輸入"Hellow"文本,設置文本顏色爲藍色,而後保存,而後使用記事本打開剛剛保存的RTF文件,此時就能看到一個最簡單的RTF文檔的內容了,其內容以下。
{編程
/rtf1/ansi/ansicpg936/deff0/deflang1033/deflangfe2052
{/fonttbl數組
{/f0/fmodern/fprq6/fcharset134 /'cb/'ce/'cc/'e5; }
}網絡
{/colortbl ; /red0/green0/blue255; }
{/*/generator Msftedit 5.41.15.1507; }
/viewkind4/uc1/pard/cf1/lang2052/f0/fs20 Hellow/cf0/par
}編輯器
此處爲了便於閱讀,對代碼進行了縮進處理,實際上RTF文檔中空白字符是會影響到顯示結果的,通常實際生成RTF文檔時不要添加額外的空白字符。函數
這段RTF代碼第一行和最後一行是表示根組的花括號,而後是"/"開頭的指令,指令名稱所有由英文字母組成,若指令後面跟着若干個數字,則這些數字就是指令的參數。好比"/rtf1",這個指令名稱是"rtf",參數值是"1";而"/ansi"指令名稱是"ansi",沒有參數。學習
指令"/rtf"是每一個RTF文檔必備的,並且老是第一個指令,所以能夠看做RTF文檔的文件頭標記。若一個RTF文檔第一個指令不是"rtf"指令,則能夠認爲這個RTF文檔是不合法的。測試
指令"/ansicpg"就是說明該RTF文檔的內容的編碼格式,參數就是編碼格式編號,例如"/ansicpg936"就是指明編碼格式爲936號字符集,對於C#程序來講,就是庫函數 System.Text.Encoding.GetEncoding( 936 ) 的返回結果,也就是GB2312編碼格式。RTF文檔自己確定是使用標準的ANSI格式保存的,此處指明的字符編碼格式是用於處理RTF文檔中的轉義字符的,好比代碼中由連續的轉義字符 /'cb/'ce/'cc/'e5 ,程序解析RTF文檔時,應當將這一串轉義字符生成一個字節數組,內容爲 0xcb,0xce,0xcc,0xe5,而後使用第936號編碼格式對象的GetString( byte[] )函數來還原所存儲的字符串,也就是"宋體"兩個字。這點比HTML的轉義字符處理要麻煩一些,HTML轉義字符是一個指令定義一個字符,而RTF中的是一個指令定義一個字節,而漢字是雙字節的字符編碼,轉化前還得設法得到完整的字節序列。字體
指令"/fonttbl"定義了文檔中使用的全部的字體的列表,RTF文本內容引用這個字體列表來得到顯示文檔使用的字體,這和HTML文檔中統必定義CSS樣式有點相似。"fonttbl"組中由若干個子組,每一個子組定義一個字體,字體定義組的第一個指令爲"/f",帶有一個參數指明字體的編號,好比"/f0"指明這個字體編號爲0,"/f1"指明字體編號爲1。字體定義組還定義了關於字體的其餘信息,其中最重要的就是最後的字體名稱了。此演示文檔中,字體的名稱就是"/'cb/'ce/'cc/'e5; ",通過編碼後就是"宋體; ",當心後面還有個分號。注意字體編號多是不連續的,好比能夠存在這樣的字體表代碼"{/f0 ...}{/f1 ...}{/f99 ...}{/f212 ...}",所以解析RTF字體表時要考慮這點。
指令"/colortbl"定義了文檔顏色表,RTF文檔是統一引用顏色值的,文檔內容的文本顏色,背景色等顏色設置都是引用顏色表的,RTF顏色表中只定義了各個顏色的RGB值,沒有明確的定義編號,引用時是按照從左到右的順序引用顏色的,並且顏色值的編號是從「1」開始計算的。此處定義了一個顏色值"/red0/green0/blue255",也就是純藍色。
指令"/*/generator"是定義了文檔的建立者,此處定義指令的方式比較特殊,採用了 "/*/"前綴,我的理解是定義了一種擴展指令,其餘的RTF文檔處理程序遇到這樣的指令能夠忽略不計。
後面的指令就是開始描述RTF文檔的正文了,好比"/pard"開始清除當前段落設置,當前段落設置爲默認格式;"/f0"表示設置當前字體爲字體表中編號爲"0"的字體;"/fs20"設置字體大小,此處的字體大小爲"20",單位是半個點(MSND是這樣說的:Font size in half-points (the default is 24));"/cf1"表示當前文本顏色採用第一號顏色,即純藍色(RTF顏色表序號從1開始計算);還有純文本數據 "Hellow"就是RTF文檔的純文本內容了。
對於英文內容,大部分是能夠直接輸出到RTF文檔中,但對於某些特殊字符須要進行字符轉義,好比"/","{","}"等等,前面得加上轉義前綴"/",所以實際上輸出的是"//","/{","/}",這相似C語言的轉義字符處理。對於製表符,得輸出"/tab",對於編碼大於256的字符,例如漢字,得使用文本內容編碼器來編碼生成二進制數據,而後使用轉義前綴"/'"來轉義輸出一個個字節編碼。好比「宋體」,它的GB2312編碼生成字節序列 0xcb,0xce,0xcc,0xe5,它輸出到RTF文檔的結果就是「/'cb/'ce/'cc/'e5」。
RTF文檔中能夠嵌入圖片,可使用代碼"{/pict ... }",圖片組中包含了圖片的二進制數據的16進制編碼字符串,MSDN中關於RTF圖片格式的說明很少,我對一些圖片數據的格式也不清楚,所以如何處理RTF圖片也沒多少可說的。
關於各類指令的詳細說明可參考MSDN中的相關文章,文章地址"ms-help://MS.MSDNQTR.2003FEB.2052/dnrtfspec/html/rtfspec_16.htm#rtfspec_21"。
咱們對RTF文檔格式有所瞭解後,就能夠開始編程來操做RTF文檔了,無非就是按照RTF格式來拼湊字符串而已。好比個人文本編輯器有個功能,能將編輯的內容保存爲RTF格式,這時候就須要根據個人文檔內容來生成RTF文檔。
首先是作一個RTF文檔書寫器,雖然生成RTF文檔的操做能夠看做拼湊RTF字符串,但在編程實踐中不能真的這麼拼湊,得仿造System.Xml.XmlWriter來作一個RTF文檔書寫器,我編了個名爲RTFWriter的RTF文檔書寫器,該書寫器內部實現了基礎的RTF文檔格式的控制,能保證輸出正確的RTF文檔,它還提供了比較方便的編程接口,便於其餘程序模塊調用。這個RTF文檔書寫器完整的C#代碼以下
///
/// RTF文檔書寫器
///
///
/// 本書寫器對生成RTF文檔提供了基礎的支持
/// 編制 袁永福 http://www.xdesigner.cn
///
public class RTFWriter : System.IDisposable
{
#region 測試代碼 ******************************************************
[System.STAThread]
static void Main()
{
TestWriteFile();
TestClipboard();
}
///
/// 測試生成RTF文件
/// 執行這個函數後可使用 MS Word 打開文件 c:/a.rtf
///
internal static void TestWriteFile( )
{
RTFWriter w = new RTFWriter( "c://a.rtf" ) ;
TestBuildRTF( w );
w.Close();
System.Windows.Forms.MessageBox.Show("好了,你能夠打開文件 c://a.rtf 了.");
}
///
/// 測試生成RTF文檔並設置到系統剪切板中
/// 執行這個函數後就能夠在 MS Word中使用粘貼操做來顯示程序生成的文檔了
///
internal static void TestClipboard()
{
System.IO.StringWriter myStr = new System.IO.StringWriter();
RTFWriter w = new RTFWriter( myStr );
TestBuildRTF( w );
w.Close();
System.Windows.Forms.DataObject data = new System.Windows.Forms.DataObject();
data.SetData( System.Windows.Forms.DataFormats.Rtf , myStr.ToString());
System.Windows.Forms.Clipboard.SetDataObject( data , true );
System.Windows.Forms.MessageBox.Show("好了,你能夠在MS Word 中粘貼文本了.");
}
///
/// 測試生成RTF文檔
///
/// RTF文檔書寫器
private static void TestBuildRTF( RTFWriter w )
{
w.Encoding = System.Text.Encoding.GetEncoding( 936 );
// 輸出文件頭
w.WriteStartGroup();
w.WriteKeyword("rtf1");
w.WriteKeyword("ansi");
w.WriteKeyword("ansicpg" + w.Encoding.CodePage );
// 輸出字體表
w.WriteStartGroup();
w.WriteKeyword("fonttbl");
w.WriteStartGroup();
w.WriteKeyword("f0");
w.WriteText("隸書; ");
w.WriteEndGroup();
w.WriteStartGroup();
w.WriteKeyword("f1");
w.WriteText("宋體; ");
w.WriteEndGroup();
w.WriteEndGroup();
// 輸出顏色表
w.WriteStartGroup();
w.WriteKeyword("colortbl");
w.WriteText("; ");
w.WriteKeyword("red0");
w.WriteKeyword("green0");
w.WriteKeyword("blue255");
w.WriteText("; ");
w.WriteEndGroup();
// 輸出正文
w.WriteKeyword("qc"); // 設置居中對齊
w.WriteKeyword("f0"); // 設置字體
w.WriteKeyword("fs30"); // 字體大小
w.WriteText("這是第一段文本 ");
w.WriteKeyword("cf1"); // 設置顏色
w.WriteText("隸書 ");
w.WriteKeyword("cf0"); // 設置爲默認顏色
w.WriteKeyword("f1"); // 設置字體
w.WriteText("居中對齊 ABC12345");
w.WriteKeyword("par"); // 開始新的段落
w.WriteKeyword("pard"); // 清除居中對齊
w.WriteKeyword("f1"); // 設置字體
w.WriteKeyword("fs20"); // 字體大小
w.WriteKeyword("cf1");
w.WriteText("這是第二段文本 宋體 左對齊 ABC12345");
// 結束輸出
w.WriteEndGroup();
}
#endregion
///
/// 初始化對象
///
/// 文本書寫器
public RTFWriter( System.IO.TextWriter w )
{
myWriter = w ;
}
///
/// 初始化對象
///
/// 文件名
public RTFWriter( string strFileName )
{
myWriter = new System.IO.StreamWriter(
strFileName ,
false ,
System.Text.Encoding.ASCII );
}
private System.Text.Encoding myEncoding = System.Text.Encoding.GetEncoding( 936 ) ;
///
/// 字符編碼格式
///
public System.Text.Encoding Encoding
{
get{ return myEncoding ; }
set{ myEncoding = value; }
}
///
/// 內置的文本書寫器
///
private System.IO.TextWriter myWriter = null;
private bool bolIndent = false;
///
/// 是否使用縮進
///
///
/// RTF文檔內部不能隨便縮進,提供此選項只是用於生成便於閱讀的RTF文檔,便於程序的調試,
/// 在開發調試中能夠設置該屬性爲true,方便開發者能直接查看生成的RTF文檔,但在生成最終運行的
/// 程序時應當設置該屬性爲 false .
///
public bool Indent
{
get{ return bolIndent ; }
set{ bolIndent = value; }
}
private string strIndentString = " ";
///
/// 縮進字符串
///
public string IndentString
{
get{ return strIndentString ; }
set{ strIndentString = value; }
}
///
/// 當前縮進層次
///
private int intGroupLevel = 0 ;
///
/// 關閉對象
///
public void Close()
{
if(this.intGroupLevel > 0 )
throw new System.Exception("還有組未寫完");
if( myWriter != null )
{
myWriter.Close();
myWriter = null;
}
}
///
/// 輸出一個組
///
/// 關鍵字
public void WriteGroup( string KeyWord )
{
this.WriteStartGroup();
this.WriteKeyword( KeyWord );
this.WriteEndGroup();
}
///
/// 開始輸出組
///
public void WriteStartGroup( )
{
if( bolIndent )
{
InnerWriteNewLine();
myWriter.Write("{");
}
else
myWriter.Write("{");
intGroupLevel ++ ;
}
///
/// 結束輸出組
///
public void WriteEndGroup()
{
intGroupLevel -- ;
if( intGroupLevel < 0 )
throw new System.Exception("組不匹配");
if( bolIndent )
{
InnerWriteNewLine();
InnerWrite("}");
}
else
InnerWrite("}");
}
///
/// 輸出原始文本
///
/// 文本值
public void WriteRaw( string txt )
{
if( txt != null && txt.Length > 0 )
{
InnerWrite( txt );
}
}
///
/// 輸出關鍵字
///
/// 關鍵字值
public void WriteKeyword( string Keyword )
{
WriteKeyword( Keyword , false );
}
///
/// 輸出關鍵字
///
/// 關鍵字值
/// 是不是擴展關鍵字
public void WriteKeyword( string Keyword , bool Ext)
{
if( Keyword == null || Keyword.Length == 0)
throw new System.ArgumentNullException("值不得爲空");
if( bolIndent == false && ( Keyword == "par" || Keyword == "pard" ) )
{
// par 或 pard 前能夠輸出空白行,不影響RTF文檔顯示
InnerWrite( System.Environment.NewLine );
}
if( this.bolIndent )
{
if( Keyword == "par" || Keyword == "pard" )
{
this.InnerWriteNewLine();
}
}
if( Ext )
InnerWrite("//*//");
else
InnerWrite("//");
InnerWrite( Keyword );
}
///
/// 內容文本編碼格式
///
private System.Text.Encoding Unicode = System.Text.Encoding.Unicode ;
///
/// 輸出純文本
///
/// 文本值
public void WriteText( string Text )
{
if( Text == null || Text.Length == 0 )
return ;
InnerWrite(' ');
for( int iCount = 0 ; iCount < Text.Length ; iCount ++ )
{
char c = Text[ iCount ] ;
if( c == '/t')
{
this.WriteKeyword("tab");
InnerWrite(' ');
}
else if( c < 256 )
{
if( c > 32 && c < 127 )
{
// 出現特殊字符,須要斜線轉義
if( c == '//' || c == '{' || c == '}' )
InnerWrite( '//');
InnerWrite( c );
}
else
{
InnerWrite("///'");
WriteByte( ( byte ) c );
}
}
else
{
byte[] bs = myEncoding.GetBytes( c.ToString());
for(int iCount2 = 0 ; iCount2 < bs.Length ; iCount2 ++ )
{
InnerWrite("///'");
WriteByte( bs[ iCount2 ] );
}
}
}//for( int iCount = 0 ; iCount < Text.Length ; iCount ++ )
}
///
/// 當前位置
///
private int intPosition = 0 ;
///
/// 當前行的位置
///
private int intLineHead = 0 ;
///
/// 16進制字符組
///
private const string Hexs = "0123456789abcdef";
///
/// 輸出字節數組
///
/// 字節數組
public void WriteBytes( byte[] bs )
{
if( bs == null || bs.Length == 0 )
return ;
WriteRaw( " " );
for( int iCount = 0 ; iCount < bs.Length ; iCount ++ )
{
if( ( iCount % 32 ) == 0 )
{
this.WriteRaw( System.Environment.NewLine );
this.WriteIndent();
}
else if( ( iCount % 8 ) == 0 )
{
this.WriteRaw(" ");
}
byte b = bs[ iCount ] ;
int h = ( b & 0xf0 ) >> 4 ;
int l = b & 0xf ;
myWriter.Write( Hexs[ h ] );
myWriter.Write( Hexs[ l ] );
intPosition += 2 ;
}
}
///
/// 輸出一個字節數據
///
/// 字節數據
public void WriteByte( byte b )
{
int h = ( b & 0xf0 ) >> 4 ;
int l = b & 0xf ;
myWriter.Write( Hexs[ h ] );
myWriter.Write( Hexs[ l ] );
intPosition += 2 ;
//FixIndent();
}
#region 內部成員 ******************************************************
private void InnerWrite( char c )
{
intPosition ++ ;
myWriter.Write( c );
}
private void InnerWrite( string txt )
{
intPosition += txt.Length ;
myWriter.Write( txt );
}
private void FixIndent()
{
if( this.bolIndent )
{
if( intPosition - intLineHead > 100 )
InnerWriteNewLine();
}
}
private void InnerWriteNewLine()
{
if( this.bolIndent )
{
if( intPosition > 0 )
{
InnerWrite( System.Environment.NewLine );
intLineHead = intPosition ;
WriteIndent();
}
}
}
private void WriteIndent( )
{
if( bolIndent )
{
for( int iCount = 0 ; iCount < intGroupLevel ; iCount ++ )
{
InnerWrite( this.strIndentString );
}
}
}
#endregion
///
/// 銷燬對象
///
public void Dispose()
{
this.Close();
}
}
你使用VS.NET新建一個C#工程項目後,刪除自動生成的Main()函數,而後複製並粘貼這段代碼,這樣就能夠編譯運行了。
在這個RTFWriter的基礎上,你能夠構造本身的RTF應用了,好比將數據庫的數據導出到RTF文檔中,使用RTF格式向其餘程序傳遞數據。筆者正在開發的XWriter文本編輯器也使用RTFWriter將編輯的文檔保存爲RTF格式,並且實際上這篇文章是徹底使用XWriter編輯的,而後導出爲HTML格式,沒有使用MS Word,FrontPage等其餘文檔編輯器,本文中的代碼是在VS.NET的C#代碼編輯器中直接複製-粘貼而得。
本文只是對操做RTF文檔提供了一些比較簡單的說明,詳細內容能夠參考MSDN中關於RTF的說明,網絡上的資源更是多如牛毛。RTF文檔格式原理簡單,但內容卻很多,它是一種很古老的技術,卻一直到如今還在普遍的使用,並且估計還能用上很長一段時期。其實咱們在學習不斷出現的新技術的時候,也能夠注意那些古老的但通過時間考驗的技術。