如何使用转义的 unicode 解码字符串?
Posted
技术标签:
【中文标题】如何使用转义的 unicode 解码字符串?【英文标题】:How do I decode a string with escaped unicode? 【发布时间】:2011-12-14 15:59:57 【问题描述】:我不确定这叫什么,所以我在搜索它时遇到了麻烦。如何使用 javascript 将带有 unicode 的字符串从 http\u00253A\u00252F\u00252Fexample.com
解码到 http://example.com
?我尝试了unescape
、decodeURI
和decodeURIComponent
,所以我想唯一剩下的就是字符串替换了。
编辑:字符串不是输入的,而是另一段代码的子字符串。所以要解决这个问题,你必须从这样的事情开始:
var s = 'http\\u00253A\\u00252F\\u00252Fexample.com';
我希望这能说明为什么 unescape() 不起作用。
【问题讨论】:
字符串从何而来? @Cameron:该字符串来自我调用 innerhtml 来获取的脚本。这就是为什么亚历克斯的答案不起作用的原因。 【参考方案1】:更新:请注意,这是一个适用于旧版浏览器或非浏览器平台的解决方案,并且出于教学目的而保持有效。请参阅下面@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
函数将字符串解码为正确的形式.
【讨论】:
谢谢。你能解释一下你在做什么吗?看起来正则表达式正在寻找\u
前缀而不是 4 个字符的十六进制数字(字母或数字)。 replace方法中的函数是如何工作的?
你说得对,这需要一个解释,所以我更新了我的帖子。享受吧!
很好的解决方案。就我而言,我将从服务器发送的所有国际(非 ascii)字符编码为转义的 unicode,然后使用浏览器中的函数将字符解码为正确的 UTF-8 字符。我发现我必须更新以下正则表达式才能捕获所有语言(即泰语)的字符:var r = /\\u([\d\w]1,)/gi;
请注意,这似乎比JSON.parse
方法慢得多:jsperf.com/unicode-func-vs-json-parse
@IoannisKaradimas 在 Javascript 中肯定存在弃用之类的东西。通过声明必须始终支持旧版浏览器来声明并支持它是完全不合历史的观点。在任何情况下,任何想要使用它并且想要避免使用unescape()
的人都可以使用decodeURIComponent()
来代替。在这种情况下,它的工作原理相同。但是,我会推荐 radicand 的方法,因为它更简单,支持和执行速度更快,结果相同(但请务必阅读 cmets)。【参考方案2】:
编辑(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
【讨论】:
有趣。我确实不得不在它周围添加引号unescape(JSON.parse('"' + s + '"'));
额外引号的原因是什么?这是否使它成为有效的 JSON?
请注意,这似乎比 fromCharCode
方法快得多:jsperf.com/unicode-func-vs-json-parse
关于@styfle 答案的重要说明:在处理不受信任的数据时不要使用JSON.parse('"' + s + '"')
,而是使用JSON.parse('"' + s.replace('"', '\\"') + '"')
,否则当输入包含引号时,您的代码会中断 .
很好的答案@alexander255,但你实际上想使用: JSON.parse('"' + str.replace(/\"/g, '\\"' + '"') 到替换整个字符串中出现的所有该字符,而不是替换一个。
对于那些遇到这种情况并担心unescape()
已被弃用的人,decodeURIComponent()
在这种情况下与unescape()
的工作方式相同,所以只需用它替换它就可以了。 【参考方案3】:
请注意,unescape()
的使用是 deprecated,例如,它不适用于 TypeScript 编译器。
根据 radicand 的回答和下面的 cmets 部分,这是一个更新的解决方案:
var string = "http\\u00253A\\u00252F\\u00252Fexample.com";
decodeURIComponent(JSON.parse('"' + string.replace(/\"/g, '\\"') + '"'));
http://example.com
【讨论】:
这对某些字符串不起作用,因为引号会破坏 JSON 字符串并导致 JSON 解析错误。在这些情况下,我使用了另一个答案 (***.com/a/7885499/249327)。【参考方案4】:我没有足够的代表把它放在现有答案的 cmets 下:
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 可能会导致错误的代码。
【讨论】:
【参考方案5】:为此使用 JSON.decode
会带来一些您必须注意的重大缺点:
JSON.decode
(将它们用双引号括起来之后)将出错,即使这些都是有效的:\\n
、\n
、\\0
、a"a
不支持十六进制转义:\\x45
不支持 Unicode 码位序列:\\u045
还有其他注意事项。从本质上讲,为此目的使用JSON.decode
是一种黑客行为,并且不会按照您可能一直期望的方式工作。您应该坚持使用 JSON
库来处理 JSON,而不是字符串操作。
我最近自己遇到了这个问题,想要一个强大的解码器,所以我自己写了一个。它是完整且经过彻底测试的,可在此处获得:https://github.com/iansan5653/unraw。它尽可能地模仿 JavaScript 标准。
说明:
源代码大约有 250 行,所以我不会在这里全部包含,但本质上它使用以下正则表达式来查找所有转义序列,然后使用 parseInt(string, 16)
解析它们以解码 base-16 数字,然后使用 @987654333 @获取对应的字符:
/\\(?:(\\)|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
【讨论】:
【参考方案6】:就我而言,我试图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
【讨论】:
【参考方案7】:这不是这个确切问题的答案,但对于那些通过搜索结果访问此页面并且试图(像我一样)在给定一系列转义代码点的情况下构造单个 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);
【讨论】:
以上是关于如何使用转义的 unicode 解码字符串?的主要内容,如果未能解决你的问题,请参考以下文章
SyntaxError :( unicode错误)'unicodeescape'编解码器无法解码位置2-3的字节:截断 UXXXXXXXX转义[重复]