VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > C#教程 >
  • c# 操作RTF文档

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  笔者正在用C#开发一个名为XWriter的文本编辑器(点击下载),其中需要提供对RTF文档的支持,以前从没有搞过RTF文档,因此临时突击研究了一下,经过几天的学习研究和实践,对C#操作RTF文档有所了解,因此才可以写出此文给予说明,希望能对其他人学习RTF文档格式有所帮助。

  RTF文档格式是微软提出的一种用于描述带格式文本的文档格式,上个世纪就提出来了,一直用到现在,而且很多程序都支持这种格式,微软的Office软件家族,Windows写字板软件等等都支持,而且Windows操作系统的剪切板和OLE拖拽操作也支持RTF文档,这样就允许不同的软件通过RTF格式相互交流带格式文本。比如我用的VS.NET2003中的C#代码编辑器,在其中复制了一段代码文本,在MSWord中粘贴所得就是具有高亮度显示的文本。因此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文档的内容了,其内容如下。

  {
   tf1ansiansicpg936deff0deflang1033deflangfe2052
  {fonttbl
  {f0fmodernfprq6fcharset134'cb'ce'cc'e5;}
  }
  {colortbl; ed0green0lue255;}
  {*generatorMsftedit5.41.15.1507;}
  viewkind4uc1pardcf1lang2052f0fs20Hellowcf0par
  }

 

  此处为了便于阅读,对代码进行了缩进处理,实际上RTF文档中空白字符是会影响到显示结果的,一般实际生成RTF文档时不要添加额外的空白字符。

  这段RTF代码第一行和最后一行是表示根组的花括号,然后是""开头的指令,指令名称全部由英文字母组成,若指令后面跟着若干个数字,则这些数字就是指令的参数。比如" tf1",这个指令名称是"rtf",参数值是"1";而"ansi"指令名称是"ansi",没有参数。

  指令" tf"是每个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”开始计算的。此处定义了一个颜色值" ed0green0lue255",也就是纯蓝色。

  指令"*generator"是定义了文档的创建者,此处定义指令的方式比较特殊,采用了"*"前缀,个人理解是定义了一种扩展指令,其他的RTF文档处理程序遇到这样的指令可以忽略不计。

  后面的指令就是开始描述RTF文档的正文了,比如"pard"开始清除当前段落设置,当前段落设置为默认格式;"f0"表示设置当前字体为字体表中编号为"0"的字体;"fs20"设置字体大小,此处的字体大小为"20",单位是半个点(MSND是这样说的:Fontsizeinhalf-points(thedefaultis24));"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#代码如下

 
  ///<summary>
///RTF文档书写器
///</summary>
///<remarks>
///本书写器对生成RTF文档提供了基础的支持
///编制袁永福http://www.xdesigner.cn
///</remarks>
publicclassRTFWriter:System.IDisposable
{
  #region测试代码******************************************************
  [System.STAThread]
  staticvoidMain()
  {
    TestWriteFile();
    TestClipboard();
  }
  ///<summary>
  ///测试生成RTF文件
  ///执行这个函数后可以使用MSWord打开文件c:a.rtf
  ///</summary>
  internalstaticvoidTestWriteFile()
  {
    RTFWriterw=newRTFWriter("c:a.rtf");
    TestBuildRTF(w);
    w.Close();
    System.Windows.Forms.MessageBox.Show("好了,你可以打开文件c:a.rtf了.");
  }
  ///<summary>
  ///测试生成RTF文档并设置到系统剪切板中
  ///执行这个函数后就可以在MSWord中使用粘贴操作来显示程序生成的文档了
  ///</summary>
  internalstaticvoidTestClipboard()
  {
    System.IO.StringWritermyStr=newSystem.IO.StringWriter();
    RTFWriterw=newRTFWriter(myStr);
    TestBuildRTF(w);
    w.Close();
    System.Windows.Forms.DataObjectdata=newSystem.Windows.Forms.DataObject();
    data.SetData(System.Windows.Forms.DataFormats.Rtf,myStr.ToString());
    System.Windows.Forms.Clipboard.SetDataObject(data,true);
    System.Windows.Forms.MessageBox.Show("好了,你可以在MSWord中粘贴文本了.");
  }
  ///<summary>
  ///测试生成RTF文档
  ///</summary>
  ///<paramname="w">RTF文档书写器</param>
  privatestaticvoidTestBuildRTF(RTFWriterw)
  {
    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
  ///<summary>
  ///初始化对象
  ///</summary>
  ///<paramname="w">文本书写器</param>
  publicRTFWriter(System.IO.TextWriterw)
  {
    myWriter=w;
  }
  ///<summary>
  ///初始化对象
  ///</summary>
  ///<paramname="strFileName">文件名</param>
  publicRTFWriter(stringstrFileName)
  {
    myWriter=newSystem.IO.StreamWriter(
      strFileName,
      false,
      System.Text.Encoding.ASCII);
  }
  privateSystem.Text.EncodingmyEncoding=System.Text.Encoding.GetEncoding(936);
  ///<summary>
  ///字符编码格式
  ///</summary>
  publicSystem.Text.EncodingEncoding
  {
    get{returnmyEncoding;}
    set{myEncoding=value;}
  }
  ///<summary>
  ///内置的文本书写器
  ///</summary>
  privateSystem.IO.TextWritermyWriter=null;
  privateboolbolIndent=false;
  ///<summary>
  ///是否使用缩进
  ///</summary>
  ///<remarks>
  ///RTF文档内部不能随便缩进,提供此选项只是用于生成便于阅读的RTF文档,便于程序的调试,
  ///在开发调试中可以设置该属性为true,方便开发者能直接查看生成的RTF文档,但在生成最终运行的
  ///程序时应当设置该属性为false.
  ///</remarks>
  publicboolIndent
  {
    get{returnbolIndent;}
    set{bolIndent=value;}
  }
  privatestringstrIndentString=" ";
  ///<summary>
  ///缩进字符串
  ///</summary>
  publicstringIndentString
  {
    get{returnstrIndentString;}
    set{strIndentString=value;}
  }
  ///<summary>
  ///当前缩进层次
  ///</summary>
  privateintintGroupLevel=0;
  ///<summary>
  ///关闭对象
  ///</summary>
  publicvoidClose()
  {
    if(this.intGroupLevel>0)
      thrownewSystem.Exception("还有组未写完");
    if(myWriter!=null)
    {
      myWriter.Close();
      myWriter=null;
    }
  }
  ///<summary>
  ///输出一个组
  ///</summary>
  ///<paramname="KeyWord">关键字</param>
  publicvoidWriteGroup(stringKeyWord)
  {
    this.WriteStartGroup();
    this.WriteKeyword(KeyWord);
    this.WriteEndGroup();
  }
  ///<summary>
  ///开始输出组
  ///</summary>
  publicvoidWriteStartGroup()
  {
    if(bolIndent)
    {
      InnerWriteNewLine();
      myWriter.Write("{");
    }
    else
      myWriter.Write("{");
    intGroupLevel++;
  }
  ///<summary>
  ///结束输出组
  ///</summary>
  publicvoidWriteEndGroup()
  {
    intGroupLevel--;
    if(intGroupLevel<0)
      thrownewSystem.Exception("组不匹配");
    if(bolIndent)
    {
      InnerWriteNewLine();
      InnerWrite("}");
    }
    else
      InnerWrite("}");
  }
  ///<summary>
  ///输出原始文本
  ///</summary>
  ///<paramname="txt">文本值</param>
  publicvoidWriteRaw(stringtxt)
  {
    if(txt!=null&&txt.Length>0)
    {
      InnerWrite(txt);
    }
  }
  ///<summary>
  ///输出关键字
  ///</summary>
  ///<paramname="Keyword">关键字值</param>
  publicvoidWriteKeyword(stringKeyword)
  {
    WriteKeyword(Keyword,false);
  }
  ///<summary>
  ///输出关键字
  ///</summary>
  ///<paramname="Keyword">关键字值</param>
  ///<paramname="Ext">是否是扩展关键字</param>
  publicvoidWriteKeyword(stringKeyword,boolExt)
  {
    if(Keyword==null||Keyword.Length==0)
      thrownewSystem.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);
  }
  ///<summary>
  ///内容文本编码格式
  ///</summary>
  privateSystem.Text.EncodingUnicode=System.Text.Encoding.Unicode;
  ///<summary>
  ///输出纯文本
  ///</summary>
  ///<paramname="Text">文本值</param>
  publicvoidWriteText(stringText)
  {
    if(Text==null||Text.Length==0)
      return;
    InnerWrite('');
    
    for(intiCount=0;iCount<Text.Length;iCount++)
    {
      charc=Text[iCount];
      if(c==' ')
      {
        this.WriteKeyword("tab");
        InnerWrite('');
      }
      elseif(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(intiCount2=0;iCount2<bs.Length;iCount2++)
        {
          InnerWrite("'");
          WriteByte(bs[iCount2]);
        }
      }
    }//for(intiCount=0;iCount<Text.Length;iCount++)
  }
  ///<summary>
  ///当前位置
  ///</summary>
  privateintintPosition=0;
  ///<summary>
  ///当前行的位置
  ///</summary>
  privateintintLineHead=0;
  ///<summary>
  ///16进制字符组
  ///</summary>
  privateconststringHexs="0123456789abcdef";
  ///<summary>
  ///输出字节数组
  ///</summary>
  ///<paramname="bs">字节数组</param>
  publicvoidWriteBytes(byte[]bs)
  {
    if(bs==null||bs.Length==0)
      return;
    WriteRaw("");
    for(intiCount=0;iCount<bs.Length;iCount++)
    {
      if((iCount%32)==0)
      {
        this.WriteRaw(System.Environment.NewLine);
        this.WriteIndent();
      }
      elseif((iCount%8)==0)
      {
        this.WriteRaw("");
      }
      byteb=bs[iCount];
      inth=(b&0xf0)>>4 ;
      intl=b&0xf;
      myWriter.Write(Hexs[h]);
      myWriter.Write(Hexs[l]);
      intPosition+=2;
    }
  }
  ///<summary>
  ///输出一个字节数据
  ///</summary>
  ///<paramname="b">字节数据</param>
  publicvoidWriteByte(byteb)
  {
    inth=(b&0xf0)>>4;
    intl=b&0xf;
    myWriter.Write(Hexs[h]);
    myWriter.Write(Hexs[l]);
    intPosition+=2;
    //FixIndent();
  }
  #region内部成员******************************************************
  privatevoidInnerWrite(charc)
  {
    intPosition++;
    myWriter.Write(c);
  }
  privatevoidInnerWrite(stringtxt)
  {
    intPosition+=txt.Length;
    myWriter.Write(txt);
  }
  privatevoidFixIndent()
  {
    if(this.bolIndent)
    {
      if(intPosition-intLineHead>100)
        InnerWriteNewLine();
    }
  }
  privatevoidInnerWriteNewLine()
  {
    if(this.bolIndent)
    {
      if(intPosition>0)
      {
        InnerWrite(System.Environment.NewLine);
        intLineHead=intPosition;
        WriteIndent();
      }
    }
  }
  privatevoidWriteIndent()
  {
    if(bolIndent)
    {
      for(intiCount=0;iCount<intGroupLevel;iCount++)
      {
        InnerWrite(this.strIndentString);
      }
    }
  }
  #endregion
  ///<summary>
  ///销毁对象
  ///</summary>
  publicvoidDispose()
  {
    this.Close();
  }
}

 

 

  你使用VS.NET新建一个C#工程项目后,删除自动生成的Main()函数,然后复制并粘贴这段代码,这样就可以编译运行了。

  在这个RTFWriter的基础上,你可以构造自己的RTF应用了,比如将数据库的数据导出到RTF文档中,使用RTF格式向其他程序传递数据。笔者正在开发的XWriter文本编辑器也使用RTFWriter将编辑的文档保存为RTF格式,而且实际上这篇文章是完全使用XWriter编辑的,然后导出为HTML格式,没有使用MSWord,FrontPage等其他文档编辑器,本文中的代码是在VS.NET的C#代码编辑器中直接复制-粘贴而得。

  本文只是对操作RTF文档提供了一些比较简单的说明,详细内容可以参考MSDN中关于RTF的说明,网络上的资源更是多如牛毛。RTF文档格式原理简单,但内容却不少,它是一种很古老的技术,却一直到现在还在广泛的使用,而且估计还能用上很长一段时期。其实我们在学习不断出现的新技术的时候,也可以注意那些古老的但经过时间考验的技术。

 

 

  我们使用Windows写字板新建一个RTF文档,只输入"Hellow"文本,设置文本颜色为蓝色,然后保存,然后使用记事本打开刚刚保存的RTF文件,此时就能看到一个最简单的RTF文档的内容了,其内容如下。

  {
   tf1ansiansicpg936deff0deflang1033deflangfe2052
  {fonttbl
  {f0fmodernfprq6fcharset134'cb'ce'cc'e5;}
  }
  {colortbl; ed0green0lue255;}
  {*generatorMsftedit5.41.15.1507;}
  viewkind4uc1pardcf1lang2052f0fs20Hellowcf0par
  }

 

  此处为了便于阅读,对代码进行了缩进处理,实际上RTF文档中空白字符是会影响到显示结果的,一般实际生成RTF文档时不要添加额外的空白字符。

  这段RTF代码第一行和最后一行是表示根组的花括号,然后是""开头的指令,指令名称全部由英文字母组成,若指令后面跟着若干个数字,则这些数字就是指令的参数。比如" tf1",这个指令名称是"rtf",参数值是"1";而"ansi"指令名称是"ansi",没有参数。

  指令" tf"是每个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中的是一个指令定义一个字节,而汉字是双字节的字符编码,转化前还得设法获得完整的字节序列。



相关教程