如何解码带有转义Unicode的字符串?

119
我不确定这个东西叫什么,所以我很难搜索。如何使用JavaScript将带有Unicode的字符串从http\u00253A\u00252F\u00252Fexample.com解码为http://example.com?我尝试了unescapedecodeURIdecodeURIComponent,所以我猜唯一剩下的就是字符串替换。
编辑:该字符串并非键入,而是来自其他代码的子字符串。因此,要解决问题,您需要从以下内容开始:
var s = 'http\\u00253A\\u00252F\\u00252Fexample.com';
我希望这可以解释为什么unescape()无法工作。

3
字符串从哪里来? - Cameron
2
@Cameron:这个字符串来自我调用了innerHTML的脚本。这就是为什么Alex的答案不起作用的原因。 - styfle
7个回答

147

编辑(2017-10-12)

@MechaLynx 和 @Kevin-Weber 指出,unescape()在非浏览器环境中已被弃用,并且在 TypeScript 中不存在。 decodeURIComponent 是一个即插即用的替代方法。为了更广泛的兼容性,请使用下面的方法:

decodeURIComponent(JSON.parse('"http\\u00253A\\u00252F\\u00252Fexample.com"'));
> 'http://example.com'

原始答案:

unescape(JSON.parse('"http\\u00253A\\u00252F\\u00252Fexample.com"'));
> 'http://example.com'

你可以把所有的工作都交给 JSON.parse


9
有趣。我确实需要在这个内容周围添加引号 unescape(JSON.parse('"' + s + '"'));。添加额外引号的原因是什么?这样做是否使其成为有效的 JSON? - styfle
1
请注意,这种方法似乎比使用“fromCharCode”更快:http://jsperf.com/unicode-func-vs-json-parse - nrabinowitz
22
@styfle的回答需要注意:在处理不可信数据时,不要使用JSON.parse('"' + s + '"'),而应该使用JSON.parse('"' + s.replace('"', '\\"') + '"'),否则当输入包含引号时,您的代码将会出错。 - ntninja
7
@alexander255 的回答很好,但实际上你应该使用:JSON.parse('"' + str.replace(/"/g, '\"' + '"')来替换字符串中所有出现的这个字符,而不仅仅是替换一个。 - C. S.
4
如果你看到这段话并担心unescape()已被弃用,那么不用担心,decodeURIComponent()在这种情况下与unescape()的作用完全相同,只需将其替换即可。不用担心,你可以放心使用它。 - mechalynx
显示剩余5条评论

128

更新:请注意,此解决方案适用于旧版浏览器或非浏览器平台,并且仅供教学目的而保留。请参考 @radicand 下面的答案获取更加实时的解决方案。


这是一个 Unicode 转义字符串。首先对字符串进行了转义,然后使用 Unicode 进行编码。要将其转换回普通字符串:

var x = "http\\u00253A\\u00252F\\u00252Fexample.com";
var r = /\\u([\d\w]{4})/gi;
x = x.replace(r, function (match, grp) {
    return String.fromCharCode(parseInt(grp, 16)); } );
console.log(x);  // http%3A%2F%2Fexample.com
x = unescape(x);
console.log(x);  // http://example.com
为了解释:我使用正则表达式查找\u0025。但是,由于我只需要该字符串的一部分来进行替换操作,因此我使用括号来隔离我要重用的部分0025。这个被隔离的部分称为组。
表达式末尾的gi部分表示它应匹配字符串中的所有实例,而不仅仅是第一个,并且匹配应不区分大小写。这看起来可能是不必要的给出的例子,但它增加了多样性。
现在,为了从一个字符串转换到另一个字符串,我需要在每个匹配的每个组上执行一些步骤,而我不能仅仅通过转换字符串来完成。有帮助的是,String.replace操作可以接受一个函数,该函数将针对每个匹配执行。该函数的返回值将替换字符串中的匹配本身。
我使用此函数接受的第二个参数,即我需要使用的组,并将其转换为等效的utf-8序列,然后使用内置的unescape函数将字符串解码为其正确的形式。

3
谢谢。你能解释一下你正在做什么吗?看起来正则表达式是在寻找\u前缀和一个4个字符的十六进制数字(字母或数字)。替换方法中的函数如何工作? - styfle
1
你说得对,这需要解释一下,所以我已经更新了我的帖子。享受吧! - Ioannis Karadimas
1
很棒的解决方案。在我的情况下,我正在将服务器发送的所有国际(非ASCII)字符编码为转义Unicode,然后在浏览器中使用您的函数来解码字符以获得正确的UTF-8字符。我发现我必须更新以下正则表达式才能捕获所有语言(例如泰语)的字符: var r = /\\u([\d\w]{1,})/gi; - Nathan Hanna
3
请注意,这种方法似乎比“JSON.parse”方法慢得多:http://jsperf.com/unicode-func-vs-json-parse - nrabinowitz
1
@IoannisKaradimas 在Javascript中确实存在弃用的概念。声称如此并以老旧浏览器必须始终得到支持来支持这一点是完全不合历史事实的观点。无论如何,任何想要使用它并且想要避免unescape()的人都可以使用decodeURIComponent()代替。在这种情况下,它的工作方式相同。然而,我建议采用radicand的方法,因为它更简单,同样受支持且执行速度更快,结果也相同(但一定要阅读评论)。 - mechalynx
显示剩余8条评论

22

注意,使用unescape()已被弃用,例如在 TypeScript 编译器中无法使用。

根据radicand的答案和下面的评论部分,这里是一个更新后的解决方案:

var string = "http\\u00253A\\u00252F\\u00252Fexample.com";
decodeURIComponent(JSON.parse('"' + string.replace(/\"/g, '\\"') + '"'));

http://example.com


这种方法对于某些字符串无效,因为引号可能破坏 JSON 字符串并导致 JSON 解析错误。在这些情况下,我使用了另一个答案(https://dev59.com/gmsz5IYBdhLWcg3weHgA#7885499)。 - nickdos

10

使用 JSON.decode 有明显的缺点,您必须了解这些缺点:

  • 您必须将字符串用双引号括起来
  • 许多字符不受支持,必须自行转义。例如,即使以下所有内容都有效,将它们包装在双引号中,并将其传递给 JSON.decode 将导致错误:\\n\n\\0a"a
  • 它不支持十六进制转义:\\x45
  • 它不支持 Unicode 代码点序列:\\u{045}

还有其他注意事项。基本上,为此目的使用 JSON.decode 是一种 hack 并且无法如您所预期的那样工作。您应该坚持使用 JSON 库处理 JSON,而不是进行字符串操作。


我最近自己遇到了这个问题并希望获得一个强大的解码器,所以我最终编写了一个。它是完整和经过全面测试的,可以在这里找到:https://github.com/iansan5653/unraw。它尽可能地模仿了 JavaScript 标准。

解释:

源代码大约有 250 行,所以我不会在这里包含全部内容,但基本上它使用以下正则表达式查找所有转义序列,然后使用 parseInt(string, 16) 解码基于十六进制的数字,再使用 String.fromCodePoint(number) 获得相应的字符:

/\\(?:(\\)|x([\s\S]{0,2})|u(\{[^}]*\}?)|u([\s\S]{4})\\u([^{][\s\S]{0,3})|u([\s\S]{0,4})|([0-3]?[0-7]{1,2})|([\s\S])|$)/g

注释(注意:此正则表达式匹配所有转义序列,包括无效的。如果字符串在JS中会引发错误,则在我的库中也会引发错误[即,'\x!!'将导致错误]):

/
\\ # All escape sequences start with a backslash
(?: # Starts a group of 'or' statements
(\\) # If a second backslash is encountered, stop there (it's an escaped slash)
| # or
x([\s\S]{0,2}) # Match valid hexadecimal sequences
| # or
u(\{[^}]*\}?) # Match valid code point sequences
| # or
u([\s\S]{4})\\u([^{][\s\S]{0,3}) # Match surrogate code points which get parsed together
| # or
u([\s\S]{0,4}) # Match non-surrogate Unicode sequences
| # or
([0-3]?[0-7]{1,2}) # Match deprecated octal sequences
| # or
([\s\S]) # Match anything else ('.' doesn't match newlines)
| # or
$ # Match the end of the string
) # End the group of 'or' statements
/g # Match as many instances as there are

示例

使用该库:

import unraw from "unraw";

let step1 = unraw('http\\u00253A\\u00252F\\u00252Fexample.com');
// yields "http%3A%2F%2Fexample.com"
// Then you can use decodeURIComponent to further decode it:
let step2 = decodeURIComponent(step1);
// yields http://example.com

2

虽然这并不是对这个问题的确切答案,但对于那些通过搜索结果进入此页面并试图(像我一样)构建一个给定转义码点序列的单个Unicode字符的人,请注意您可以向 String.fromCodePoint() 传递多个参数,如下所示:

String.fromCodePoint(parseInt("1F469", 16), parseInt("200D", 16), parseInt("1F4BC", 16)) // ‍

当然,您可以解析字符串以提取十六进制代码点字符串,然后执行以下操作:
let codePoints = hexCodePointStrings.map(s => parseInt(s, 16));
let str = String.fromCodePoint(...codePoints);

2

我没有足够的声望将此放在现有答案下面的评论中:

unescape 只被弃用于处理 URI(或任何编码的 utf-8),这可能是大多数人需要的情况。 encodeURIComponent 将 js 字符串转换为转义的 UTF-8,decodeURIComponent 仅适用于已转义的 UTF-8 字节。对于像 decodeURIComponent('%a9'); // error 这样的内容,它会抛出错误,因为扩展 ASCII 不是有效的 utf-8(尽管仍然是一个 Unicode 值),而 unescape('%a9'); // © 因此,在使用 decodeURIComponent 时,您需要了解您的数据。

decodeURIComponent 不会对 "%C2" 或任何单个字节超过 0x7f 的字符起作用,因为在 utf-8 中,这表示部分代理项。但是,decodeURIComponent("%C2%A9") //gives you © Unescape 在这种情况下无法正常工作 // © 并且不会抛出错误,因此如果您不了解您的数据,则 unescape 可能会导致有 bug 的代码。


0
在我的情况下,我正在尝试类似于unescape HTML文件的东西。
"\u003Cdiv id=\u0022app\u0022\u003E\r\n    \u003Cdiv data-v-269b6c0d\u003E\r\n        \u003Cdiv data-v-269b6c0d class=\u0022menu\u0022\u003E\r\n    \u003Cdiv data-v-269b6c0d class=\u0022faux_column\u0022\u003E\r\n        \u003Cdiv data-v-269b6c0d class=\u0022row\u0022\u003E\r\n            \u003Cdiv data-v-269b6c0d class=\u0022col-md-12\u0022\u003E\r\n"  

<div id="app">
    <div data-v-269b6c0d>
        <div data-v-269b6c0d class="menu">
    <div data-v-269b6c0d class="faux_column">
        <div data-v-269b6c0d class="row">
            <div data-v-269b6c0d class="col-md-12">

以下代码在我的情况下有效:

const jsEscape = (str: string) => {
  return str.replace(new RegExp("'", 'g'),"\\'");
}

export const decodeUnicodeEntities = (data: any) => {
  return unescape(jsEscape(data));
}

// Use it
const data = ".....";
const unescaped = decodeUnicodeEntities(data); // Unescaped html


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