【问题标题】:How to use Unicode::Normalize to create most compatible windows-1252 encoded string?如何使用 Unicode::Normalize 创建最兼容的 windows-1252 编码字符串?
【发布时间】:2019-06-13 02:01:15
【问题描述】:

我有一个在 Perl 中处理 XML 编码的遗留应用程序,最有可能是 UTF-8,它需要将该 XML 的一些数据存储在某个数据库中,由于历史原因,该数据库使用 windows-1252。是的,这个设置不能支持 Unicode 标准的所有可能字符,但实际上我不需要,并且可以尝试合理兼容。

目前的具体问题是一个包含LATIN SMALL LETTER U, COMBINING DIAERESISU+0075 U+0308)的文件,这使得Perl将Unicode字符串的现有编码打破为windows-1252,但以下异常:

"\x{0308}" 不映射到 cp1252

我能够使用Unicode::Normalize::NFKC 解决这个问题,它创建了字符U+00FC (ü),它完美地映射到windows-1252。这当然会导致其他一些问题,例如如果是字符 VULGAR FRACTION ONE HALF (½, U+00BD),因为 NFKC 会为此创建 DIGIT ONE, FRACTION SLASH, DIGIT TWO (1/2, U+0031 U+2044 U+0032),然后 Perl 又会死掉:

"\x{2044}" 不映射到 cp1252

根据normalization rules 的说法,这对于NFKC 来说非常好。我使用它是因为我认为它会给我最兼容的结果,但那是错误的。改用NFC 解决了这两个问题,因为在这种情况下,这两个字符都提供了与windows-1252 兼容的normalization

这种方法对于通常可以使用与windows-1252 兼容的规范化的字符(仅与NFC 不同)会带来额外的问题。一个例子是LATIN SMALL LIGATURE FI (, U+FB01)。根据它的normalization rulesNFC之后的表示与windows-1252不兼容,而这次使用NFKC导致两个字符与windows-1252兼容:fiU+0066 U+0069)。

我目前的方法是简单地尝试编码为windows-1252,如果失败我使用NFC,然后再试一次,如果失败我使用NFKC,然后再试一次,如果失败我我现在要放弃了。这适用于我目前正在处理的情况,但如果我上面示例的所有三个字符同时出现在字符串中,显然会失败。无论NFCNFKC 的顺序如何,总会有一个字符导致windows-1252-incompatible 输出。唯一的问题是哪个字符何时中断。

但重要的一点是每个字符本身都可以规范化为与windows-1252 兼容的东西。似乎没有一劳永逸的解决方案。

那么,我是否缺少一些已经以最向后兼容的方式进行转换的 API?

如果不是,我需要自己实现什么方法来支持一个字符串中的所有上述字符?

听起来我需要按 Unicode 字符处理每个字符串 Unicode 字符,使用与 windows-1252 最兼容的内容单独标准化,然后再次连接结果。是否有一些可用的增量 Unicode 字符解析器可以处理字符和内容的组合?一个简单的基于 Unicode 字符的正则表达式是否已经处理了这个问题?

Unicode::Normalize 提供了额外的功能来处理partial strings 等,但我必须承认我目前并不完全理解它们的目的。这些示例也关注连接,但据我了解,我首先需要一些解析才能以不同方式规范化各个字符。

【问题讨论】:

  • 在整个字符串(我认为是单词)的转换失败的情况下,您可以按字符进行吗?它确实正确读取了 utf-8(对吗?),所以你有字符。也许不漂亮,但它应该照顾它?
  • 在正确处理字符和内容的组合时,您对将某些字符串拆分为单独的 Unicode 字符有何建议?我目前对那部分有点不确定。
  • 由于您似乎已经控制了转换(到 cp-1252 编码)本身,我假设它是关于识别用于转换的“字符”,以及所有 Unicode 标记等等。那将是正则表达式中的\X,它匹配逻辑字符(作为单个或多个代码点)。

标签: string perl unicode utf-8


【解决方案1】:

我不认为您缺少 API,因为它涉及到尽力而为的方法。我会尝试以下方法:

  • 使用 NFC 标准化。这结合了分解的序列,如 LATIN SMALL LETTER U、COMBINING DIAERESIS。
  • 使用正则表达式/\PM/g 提取所有未组合标记的代码点。这会丢弃 NFC 转换后剩余的所有组合标记,这些标记无论如何都无法转换为 Windows-1252。然后对于每个代码点:
    • 如果代码点可以转换为 Windows-1252,请执行此操作。
    • 否则尝试使用 NFKC 规范化代码点。如果 NFKC 映射与输入不同,则在结果字符串上递归应用所有步骤。这可以处理连字之类的事情。
    • 作为奖励:如果代码点在 NFKC 下是不变的,则转换为 NFD 并尝试将结果的第一个代码点转换为 Windows-1252。这会将 Ĝ 等字符转换为 G。
    • 否则忽略该字符。

当然还有其他方法可以将不受支持的字符转换为看起来相似的字符,但它们需要手动创建映射。

【讨论】:

  • 暂时接受这个答案,因为它是第一个并且似乎是关于我关于“最兼容”方式的问题的更完整的方法。
【解决方案2】:

由于您似乎可以根据需要转换单个字符(转换为 cp-1252 编码),因此一种方法是按照建议逐个字符处理,一旦某个单词在该过程中失败。

Perl 正则表达式中的\X 匹配逻辑 Unicode 字符extended grapheme cluster,可以作为单个代码点或序列。因此,如果您确实可以将所有单个(逻辑)字符转换为所需的编码,那么使用

while ($word =~ /(\X)/g) { ... }

您可以访问逻辑字符并将您的工作程序应用于每个字符。

如果您无法处理可能出现的所有逻辑字符,请使用特定的 character properties 将等价的 \X 拼凑在一起,以便通过组合标记等获得更精细的粒度(例如 /((.)\p{Mn}?)/\p{Nonspacing_Mark} )。完整的列表位于perluniprops

【讨论】:

  • @DragosTrif 如果评论是针对 OP 的,你应该有 at-username 以便他们得到通知(就像我在这个中一样)。无论哪种方式,我完全假设他们的实际转换下降了,他们的问题是他们的程序可能对整个单词失败,因为它可能不会以相同的方式对单词中的所有字符起作用;这就是问题的真正含义。如果确实如此,那么您友好地链接的帖子对他们没有帮助(这个答案当然不需要)。
  • @zdim 不过,阅读Text::Unidecode 很有趣。不过,您的答案中的\p{cM} 是什么?我找不到,只有\p{Mc} 用于Spacing_Mark。只是一个错字?
  • @ThorstenSchöning 确实,一个错误的错字,我的意思是 Nonspacing_Mark - 谢谢!固定
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 2015-12-03
  • 2012-09-08
相关资源
最近更新 更多