【问题标题】:Javascript: compare two strings with actually different encodingJavascript:比较两个具有实际不同编码的字符串
【发布时间】:2021-09-14 12:23:13
【问题描述】:

我在 Javascript 中比较两个可能不同编码的文件名,希望找到匹配项:

分析

在 javascript 控制台中比较日志输出时,这些文件名看起来完全相同:

15 - Beschänkt und gsägnet - PLAYBACKVERSION.mp3
15 - Beschänkt und gsägnet - PLAYBACKVERSION.mp3

注意德语变音符号。

现在,当我将这些字符串复制并粘贴到 Notepad++ 并启用十六进制编辑器时,它看起来像这样:

  • 在第一种情况下,A-Umlaut 使用 3(三)个字节编码
  • 在第二种情况下,元音变音符号仅使用 2(两个)字节进行编码。

问题

如何安全地比较这两个字符串。 Javascript 中是否有一个通用的“unencode”方法可以处理这些实例? 或者我应该/必须猜测每个编码,然后明确比较?

注意

【问题讨论】:

    标签: javascript encoding string-comparison


    【解决方案1】:

    这里发生了什么?

    如果您在 JavaScript 中有一个 String,它是一个 Unicode 代码点序列。某些组件已经将 ZIP 或 plist 中表示这些字符串的字节解码为一系列代码点。

    也就是说,这个问题不完全是关于编码,而是关于 Unicode 分解和规范化形式。

    可以在 Unicode 中以(至少)两种不同的方式对 ä 进行编码(由于有用的输出,下面的 Python 示例)。

    >>> "ä".encode("UTF-8")
    b'\xc3\xa4'  # two bytes
    >>> [ord(c) for c in "ä"]
    [228]
    >>> [unicodedata.name(c) for c in "ä"]
    ['LATIN SMALL LETTER A WITH DIAERESIS']
    

    或采用 NFKD 规范化形式,采用 UTF-8 格式的两个代码点和三个字节。

    >>> unicodedata.normalize("NFKD", "ä").encode("UTF-8")
    b'a\xcc\x88'  # three bytes
    >>> [ord(c) for c in unicodedata.normalize("NFKD", "ä")]
    [97, 776]  # two codepoints
    >>> [unicodedata.name(c) for c in unicodedata.normalize("NFKD", "ä")]
    ['LATIN SMALL LETTER A', 'COMBINING DIAERESIS']
    

    回答

    长话短说,在 JavaScript 中,在尝试常规比较之前,您需要调用 String#normalize() 以确保字符串采用相同的规范化形式。

    $ node
    Welcome to Node.js v16.6.1.
    Type ".help" for more information.
    > var a = '15 - Beschänkt und gsägnet - PLAYBACKVERSION.mp3';
    undefined
    > var b = '15 - Beschänkt und gsägnet - PLAYBACKVERSION.mp3';
    undefined
    > a.length
    50
    > b.length
    48
    > a === b
    false
    > a.normalize() === b.normalize()
    true
    >
    

    【讨论】:

    • “我特意要求使用 javascript 解决方案” - OP.
    • @Rojo 是的。这就是最后的链接。示例在 Python 中,因为它为不同的规范化形式提供了有用的可视化。
    • 奇怪的是,JavaScript 字符串是 UTF-16,而 JavaScript 中带分号的小写“a”是 0x00E4,一个 UTF-16 代码点。
    • @Pointy UTF-16 是 JavaScript 使用的内部内存表示,这里不考虑(因为我们不是在谈论代码点 >= 0xFFFF)。 0xE4 是十进制的 228,与 Python 插图中的代码点相同。
    • 因为二进制数据已经被分解开始是我的猜测。由于 OP 正在谈论 Apple plist,我敢打赌 ZIP 也是在 Mac 上创建的,并且 Mac 文件系统将文件名存储在 NFD 中。您可以使用String.fromCodePoint(97) + String.fromCodePoint(776) 来模拟它。
    猜你喜欢
    • 2017-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-04
    • 2014-11-24
    • 2020-01-30
    • 2021-07-19
    相关资源
    最近更新 更多