在Python中将Unicode文本输出到RTF文件

4

我正在尝试从Python脚本向RTF文件输出Unicode文本。背景信息如下:维基百科说:

对于 Unicode 转义,使用控制字 \u,后跟一个 16 位有符号十进制整数,表示 Unicode UTF-16 代码单元编号。为了让没有 Unicode 支持的程序也能够正常工作,必须在该数字之后指定该字符在指定代码页中最接近的表示方法。例如,\u1576? 将给出阿拉伯字母bāʼ ب,并指定不支持 Unicode 的旧程序应将其呈现为问号。

还有 这个Java输出RTF的问题这个C#输出RTF的问题

然而,我无法弄清楚如何从Python中将Unicode代码点输出为“带Unicode UTF-16代码单元编号的16位有符号十进制整数”。我尝试了这个:

for char in unicode_string:
    print '\\' + 'u' + ord(char) + '?',

但是,当在文字处理器中打开时,输出只呈现为乱码;问题似乎是它不是UTF-16代码编号。但不确定如何获得它;虽然可以编码为utf-16,但如何获得代码编号呢?
顺便说一句,PyRTF不支持Unicode(它被列为“待办事项”),虽然pyrtf-NG应该支持Unicode,但该项目似乎没有得到维护,并且文档很少,因此我对在准生产系统中使用它持谨慎态度。
编辑:我的错误。上面的代码有两个错误 - 正如Wobble所指出的那样,字符串必须是Unicode字符串,而不是已经编码的字符串,并且上面的代码会产生字符之间带有空格的结果。正确的代码是这样的:
convertstring=""
for char in unicode(<my_encoded_string>,'utf-8'):
    convertstring = convertstring + '\\' + 'u' + str(ord(char)) + '?'

这个方法很有效,在OpenOffice下至少可以工作。我将其留在这里供其他人参考(在下面的讨论中进行了进一步纠正)。

1
ShankarG:微软的实际规范并没有使用维基百科的“16位有符号十进制整数”措辞(这很好,因为与WP编辑器不同,微软的人知道没有负的Unicode代码点,并且提到它是有符号的是愚蠢的)。你只需要从中获取的是,你想要\u后跟一个最大为32767的数字。 - Wooble
1
ord() 似乎为我生成了 1576。你确定你的是 Unicode 字符串而不是 UTF-8 字节吗? - Wooble
你使用的是Unicode字符串还是UTF-8编码的字节字符串?请给我们展示一个你尝试输出的实际字符串的repr示例。 - Mark Ransom
我仍然看到你最新代码示例中的一个问题 - 如果你真的有一个编码字符串,你应该使用my_encoded_string.decode('utf8')而不是unicode(my_encoded_string)。此外,这将转换每个字符,即使它是ASCII。 - Mark Ransom
@Wooble:RTF文档(至少是较新的文档)中始终使用无符号整数;文档中提到,代码点超过32767的字符需要进行调整(减去65536)。这适用于RTF控制码以及\u控制码。 - Martijn Pieters
显示剩余3条评论
2个回答

3

根据您最新编辑的信息,我认为此函数将正常工作。除非查看下面改进的版本。

def rtf_encode(unistr):
    return ''.join([c if ord(c) < 128 else u'\\u' + unicode(ord(c)) + u'?' for c in unistr])

>>> test_unicode = u'\xa92012'
>>> print test_unicode
©2012
>>> test_utf8 = test_unicode.encode('utf-8')
>>> print test_utf8
©2012
>>> print rtf_encode(test_utf8.decode('utf-8'))
\u169?2012

这里有一个更易于理解的版本,将其分解成几个部分。 我还使其一致地返回ASCII字符串,而不是保留Unicode并在join时出错。 根据评论,它还包含了一个修复。

def rtf_encode_char(unichar):
    code = ord(unichar)
    if code < 128:
        return str(unichar)
    return '\\u' + str(code if code <= 32767 else code-65536) + '?'

def rtf_encode(unistr):
    return ''.join(rtf_encode_char(c) for c in unistr)

谢谢你。你说得对,我的代码转换了每个字符而不仅仅是非ASCII字符,但实际上这应该不会影响最终输出(在理想的情况下 :))。关于使用“decode”而不是“unicode”,根据这里,两者具有相同的功能,但你是正确的,我应该明确指定编码,比如说unicode(<my_encoded_string>,'utf-8')。 - ShankarG
@ShankarG,我没有意识到unicode函数有额外的参数,我只是认为当你给它非ASCII字符时它会失败。谢谢你告诉我。 - Mark Ransom
实际上,这仍然是不正确的。RTF标准使用有符号16位整数,因此超过32767的值表示为负数(减去65536)。 - Martijn Pieters
@MartijnPieters,我不知道为什么这么久才看到你的评论。希望我的编辑是完全修复了问题。 - Mark Ransom
@MarkRansom:这看起来肯定更好。 :-) - Martijn Pieters

1

Mark Ransom的回答并不完全正确,因为它不能正确地编码U+7fff以上的代码点,也不会转义低于0x20的字符,这是RTF标准推荐的。

我创建了一个简单的模块,将Python Unicode编码为RTF控制代码,称为rtfunicode,并且在我的博客上写了关于这个主题的文章

总之,我的方法使用正则表达式将正确的代码点映射到适合包含在PyRTF或pyrtf-ng中的RTF控制代码。


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