使用 C# 編程對 RTF 文檔進行操作 [轉載]

來源:互聯網
上載者:User

筆者正在用C#開發一個名為XWriter的文字編輯器,其中需要提供對RTF文檔的支援,以前從沒有搞過RTF文檔,因此臨時突擊研究了一下,經過幾天的學習研究和實踐,對C#操作RTF文檔有所瞭解,因此才可以寫出此文給予說明,希望能對其他人學習RTF文檔格式有所協助。

  RTF文檔格式是微軟提出的一種用於描述帶格式文本的文檔格式,上個世紀就提出來了,一直用到現在,而且很多程式都支援這種格式,微軟的Office軟體家族,Windows寫字板軟體等等都支援,而且Windows作業系統的剪下板和OLE拖拽操作也支援RTF文檔,這樣就允許不同的軟體通過RTF格式相互交流帶格式文本。比如我用的VS.NET2003中的C#代碼編輯器,在其中複製了一段代碼文本,在MS Word中粘貼所得就是具有高亮度顯示的文本。因此RTF格式的作用還是不小的,而且RTF格式是純文字格式,不是二進位格式,讀寫都不算難。

  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文檔格式原理簡單,但內容卻不少,它是一種很古老的技術,卻一直到現在還在廣泛的使用,而且估計還能用上很長一段時期。其實我們在學習不斷出現的新技術的時候,也可以注意那些古老的但經過時間考驗的技術。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.