如何使用C#将Unicode字符串输出到RTF?

23

我正在尝试将Unicode字符串输出到RTF格式中 (使用c#和winforms)。

根据维基百科(链接)

如果需要Unicode转义,则使用控制字\u,后跟一个16位有符号十进制整数,表示Unicode代码点编号。为了使不支持Unicode的程序也能正常显示,这必须跟随指定代码页中该字符的最近表示形式。例如,\u1576? 将给出阿拉伯字母beh,并指定旧版不支持Unicode的程序应将其呈现为问号。

我不知道如何将Unicode���符转换为Unicode代码点 ("\u1576")。 UTF-8、UTF-16等的转换很容易,但我不知道如何转换为代码点。

我使用此功能的场景:

  • 我将现有的RTF文件读入字符串(读取模板)
  • 用MyUnicodeString替换#TOKEN#(填充数据)
  • 将结果写入另一个RTF文件。

当Unicode字符出现时,问题就会出现。

4个回答

30

如果你需要处理的所有字符都存在于基本多语言平面中(你很少需要更多),那么简单的UTF-16编码就足够了。

维基百科:

从U+0000到U+10FFFF的所有可能代码点,除了代理代码点U+D800-U+DFFF(它们不是字符),无论当前或将来的字符分配或使用情况如何,都可以由UTF-16唯一映射。

以下示例程序演示了如何进行类似于您所需的操作:

static void Main(string[] args)
{
    // ë
    char[] ca = Encoding.Unicode.GetChars(new byte[] { 0xeb, 0x00 });
    var sw = new StreamWriter(@"c:/helloworld.rtf");
    sw.WriteLine(@"{\rtf
{\fonttbl {\f0 Times New Roman;}}
\f0\fs60 H" + GetRtfUnicodeEscapedString(new String(ca)) + @"llo, World!
}"); 
    sw.Close();
}

static string GetRtfUnicodeEscapedString(string s)
{
    var sb = new StringBuilder();
    foreach (var c in s)
    {
        if (c <= 0x7f)
            sb.Append(c);
        else
            sb.Append("\\u" + Convert.ToUInt32(c) + "?");
    }
    return sb.ToString();
}

关键点在于Convert.ToUInt32(c),它本质上返回了问题中字符的代码点值。RTF转义Unicode需要十进制Unicode值。System.Text.Encoding.Unicode编码对应于MSDN文档中的UTF-16。


嗯嗯,非常有趣的观点。如果那是真的,那么我的逻辑里可能有错误……Ian Kemp的回答更有道理……我会继续谷歌搜索。 - Emir

26

接受答案后的修正代码-添加了特殊字符转义,如这个 链接 中所述

static string GetRtfUnicodeEscapedString(string s)
{
    var sb = new StringBuilder();
    foreach (var c in s)
    {
        if(c == '\\' || c == '{' || c == '}')
            sb.Append(@"\" + c);
        else if (c <= 0x7f)
            sb.Append(c);
        else
            sb.Append("\\u" + Convert.ToUInt32(c) + "?");
    }
    return sb.ToString();
}

2
您需要将字符串转换为byte[]数组(使用Encoding.Unicode.GetBytes(string)),然后循环遍历该数组,并在找到所有Unicode字符时在其前面添加\u字符。当您将数组转换回字符串时,必须将Unicode字符保留为数字。
例如,如果您的数组如下所示:
byte[] unicodeData = new byte[] { 0x15, 0x76 };

它将变为:

// 5c = \, 75 = u
byte[] unicodeData = new byte[] { 0x5c, 0x75, 0x15, 0x76 };

你好,感谢回复。我尝试了你的解决方案,但不幸的是它并没有起作用。我认为这是因为Codepoint和UTF16编码(Encoding.Unicode)之间存在差异。你建议我从UTF16编码中输出字节,而期望的是Codepoint。(对于许多字符来说这是有效的,但不是全部) - Emir
这个答案似乎也可以工作,我在测试时可能有一个错误。 感谢您的回答和时间。 - Emir
这里唯一的问题是当你转换为字节数组时,你会失去编码。最好将其保留为UTF-16并循环遍历它。 - Brain2000

0

根据规范,以下是一些经过测试并可用的Java代码:

  public static String escape(String s){
        if (s == null) return s;

        int len = s.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++){
            char c = s.charAt(i);
            if (c >= 0x20 && c < 0x80){
                if (c == '\\' || c == '{' || c == '}'){
                    sb.append('\\');
                }
                sb.append(c);
            }
            else if (c < 0x20 || (c >= 0x80 && c <= 0xFF)){
                sb.append("\'");
                sb.append(Integer.toHexString(c));
            }else{
                sb.append("\\u");
                sb.append((short)c);
                sb.append("??");//two bytes ignored
            }
        }
        return sb.toString();
 }

重要的是,您需要在转义的Unicode字符后附加2个字符(靠近Unicode字符或只使用?代替),因为Unicode占用2个字节。
此外,规范指出,如果代码点大于32767,则应使用负值,但在我的测试中,如果不使用负值也可以。
以下是规范:
\uN:此关键字表示单个Unicode字符,该字符基于当前ANSI代码页没有等效的ANSI表示。 N表示以十进制数表示的Unicode字符值。 紧接着此关键字是ANSI表示中的等效字符。通过这种方式,旧读者将忽略\uN关键字并正确获取ANSI表示。当遇到此关键字时,读者应忽略下一个N个字符,其中N对应于上次遇到的\ucN值。

和所有RTF关键词一样,可能存在一个关键字终止空格(在ANSI字符之前),该空格不计入要跳过的字符数。虽然这种情况不太可能发生(或建议),但\bin关键字、其参数和随后的二进制数据被认为是一个用于跳过目的的字符。如果在扫描可跳过数据时遇到RTF作用范围分隔符字符(即打开或关闭大括号),则跳过的数据被视为在定界符之前结束。这使得读者可以执行一些基本的错误恢复。要在可跳过的数据中包含RTF定界符,必须使用适当的控制符号表示它(即用反斜杠转义),就像普通文本一样。任何RTF控制词或符号都被视为计算可跳过字符的单个字符。

当遇到没有相应ANSI字符的Unicode字符时,RTF写入器应输出\uN,后面跟着它能够处理的最佳ANSI表示形式。此外,如果Unicode字符翻译成的ANSI字符流的字节计数与当前Unicode字符字节计数不同,则应在\uN关键字之前发出\ucN关键字,以通知读者更改。

RTF控制字符通常采用带符号的16位数字作为参数。因此,大于32767的Unicode值必须表示为负数。


这基本正确,但需要注意的是,在\uN之后跳过的字符数(在您的情况下是问号)取决于最后一个\ucN指令:"阅读器应该忽略接下来的N个字符,其中N对应于最后一个遇到的\ucN值。"这个值默认为1,所以除非您在文件中已经覆盖了它,否则您只需要添加一个问号即可。 - Ontonator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接