您的当前位置:首页正文

C#操作RTF

2023-04-03 来源:独旅网
C#操作RTF文档 袁永福 2007-10-20

笔者正在用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\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语言的转义字符处理。对于制表符,得输出\"\ab\对于编码大于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 == '\') {

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文档格式原理简单,但内容却不少,它是一种很古老的技术,却一直到现在还在广泛的使用,而且估计还能用上很长一段时期。其实我们在学习不断出现的新技术的时候,也可以注意那些古老的但经过时间考验的技术。

因篇幅问题不能全部显示,请点此查看更多更全内容