Comment décoder une chaîne avec unicode échappé?

89

Je ne sais pas comment cela s'appelle donc j'ai du mal à le rechercher. Comment puis-je décoder une chaîne avec unicode de http\u00253A\u00252F\u00252Fexample.comà http://example.comavec JavaScript? J'ai essayé unescape, decodeURIet decodeURIComponentje suppose que la seule chose qui reste est le remplacement de la chaîne.

EDIT: La chaîne n'est pas typée, mais plutôt une sous-chaîne d'un autre morceau de code. Donc, pour résoudre le problème, vous devez commencer par quelque chose comme ceci:

var s = 'http\\u00253A\\u00252F\\u00252Fexample.com';

J'espère que cela montre pourquoi unescape () ne fonctionne pas.

styfle
la source
D'où vient la chaîne?
Cameron
@Cameron: La chaîne provient d'un script que j'ai appelé innerHTML pour obtenir. C'est pourquoi la réponse d'Alex ne fonctionne pas.
styfle

Réponses:

109

Modifier (2017-10-12) :

@MechaLynx et @ Kevin-Weber notent qu'il unescape()est déconseillé dans les environnements non-navigateur et n'existe pas dans TypeScript. decodeURIComponentest un remplacement instantané. Pour une compatibilité plus large, utilisez plutôt ce qui suit:

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

Réponse originale:

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

Vous pouvez décharger tout le travail vers JSON.parse

radicand
la source
6
Intéressant. J'ai dû ajouter des citations autour. unescape(JSON.parse('"' + s + '"'));Quelle est la raison des citations supplémentaires? Cela rend-il JSON valide?
styfle
1
Notez que cela semble être nettement plus rapide que l' fromCharCodeapproche: jsperf.com/unicode-func-vs-json-parse
nrabinowitz
17
Remarque importante à propos de la réponse de @ styfle: ne pas utiliser JSON.parse('"' + s + '"')lorsque vous traitez avec des données non approuvées à la JSON.parse('"' + s.replace('"', '\\"') + '"')place, sinon votre code sera interrompu lorsque l'entrée contient des guillemets.
ntninja
7
Excellente réponse @ alexander255, mais vous voudriez en fait utiliser: JSON.parse ('"' + str.replace (/ \" / g, '\\ "' + '"') pour remplacer TOUTES les occurrences de ce caractère dans le chaîne, plutôt que d'en remplacer une.
CS
2
Pour ceux qui rencontrent ceci et sont inquiets parce que cela unescape()a été obsolète, decodeURIComponent()fonctionne de la même manière que unescape()dans ce cas, alors remplacez-le simplement par cela et vous êtes bon.
mechalynx
116

MISE À JOUR : Veuillez noter qu'il s'agit d'une solution qui devrait s'appliquer aux navigateurs plus anciens ou aux plates-formes non-navigateur, et qui est conservée à des fins pédagogiques. Veuillez vous référer à la réponse de @radicand ci-dessous pour une réponse plus à jour.


Il s'agit d'une chaîne d'échappement unicode. La chaîne a d'abord été échappée, puis encodée avec unicode. Pour revenir à la normale:

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

Pour expliquer: j'utilise une expression régulière pour rechercher \u0025. Cependant, comme je n'ai besoin que d'une partie de cette chaîne pour mon opération de remplacement, j'utilise des parenthèses pour isoler la partie que je vais réutiliser 0025. Cette partie isolée s'appelle un groupe.

La gipartie à la fin de l'expression indique qu'elle doit correspondre à toutes les instances de la chaîne, pas seulement la première, et que la correspondance doit être insensible à la casse. Cela peut sembler inutile étant donné l'exemple, mais cela ajoute de la polyvalence.

Maintenant, pour convertir d'une chaîne à l'autre, je dois exécuter quelques étapes sur chaque groupe de chaque correspondance, et je ne peux pas le faire en transformant simplement la chaîne. Heureusement, l'opération String.replace peut accepter une fonction, qui sera exécutée pour chaque correspondance. Le retour de cette fonction remplacera la correspondance elle-même dans la chaîne.

J'utilise le deuxième paramètre que cette fonction accepte, qui est le groupe que je dois utiliser, et je le transforme en séquence utf-8 équivalente, puis j'utilise la unescapefonction intégrée pour décoder la chaîne sous sa forme appropriée.

Ioannis Karadimas
la source
3
Merci. Pourriez-vous expliquer un peu ce que vous faites? Il semble que l'expression régulière recherche un \upréfixe et un nombre hexadécimal à 4 caractères (lettres ou chiffres). Comment fonctionne la fonction de la méthode de remplacement?
styfle
1
Vous avez raison, cela nécessitait une explication, alors j'ai mis à jour mon message. Prendre plaisir!
Ioannis Karadimas
1
Excellente solution. Dans mon cas, j'encode tous les caractères internationaux (non-ascii) envoyés depuis le serveur comme unicode échappé, puis j'utilise votre fonction dans le navigateur pour décoder les caractères en caractères UTF-8 corrects. J'ai trouvé que je devais mettre à jour l'expression régulière suivante afin d'attraper des personnages de toutes les langues (c'est-à-dire thaï):var r = /\\u([\d\w]{1,})/gi;
Nathan Hanna
2
Notez que cela semble être significativement plus lent que l' JSON.parseapproche: jsperf.com/unicode-func-vs-json-parse
nrabinowitz
1
@IoannisKaradimas Il y a certainement une chose telle que la dépréciation en Javascript. Le prétendre et le soutenir en déclarant que les navigateurs plus anciens doivent toujours être pris en charge est une perspective complètement anhistorique. Dans tous les cas, quiconque veut utiliser ceci et veut également éviter unescape()peut utiliser à la decodeURIComponent()place. Cela fonctionne à l'identique dans ce cas. Je recommanderais cependant l'approche de radicand, car elle est plus simple, tout aussi prise en charge et plus rapide à exécuter, avec les mêmes résultats (assurez-vous de lire les commentaires cependant).
mechalynx
21

Notez que l'utilisation de unescape()est obsolète et ne fonctionne pas avec le compilateur TypeScript, par exemple.

Sur la base de la réponse de radicand et de la section commentaires ci-dessous, voici une solution mise à jour:

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

http://example.com

Kevin Weber
la source
Cela ne fonctionne pas pour certaines chaînes, car les guillemets peuvent casser la chaîne JSON et entraîner des erreurs d'analyse JSON. J'ai utilisé l'autre réponse ( stackoverflow.com/a/7885499/249327 ) dans ces cas.
nickdos le
2

Je n'ai pas assez de représentants pour mettre ceci sous les commentaires des réponses existantes:

unescapen'est obsolète que pour travailler avec des URI (ou n'importe quel utf-8 encodé), ce qui est probablement le cas pour les besoins de la plupart des gens. encodeURIComponentconvertit une chaîne js en UTF-8 échappé et decodeURIComponentne fonctionne que sur les octets UTF-8 échappés. Cela génère une erreur pour quelque chose comme decodeURIComponent('%a9'); // errorparce que l'ascii étendu n'est pas valide utf-8 (même si c'est toujours une valeur unicode), alors que unescape('%a9'); // ©vous devez connaître vos données lorsque vous utilisez decodeURIComponent.

decodeURIComponent ne fonctionnera pas sur "%C2"ou sur tout octet isolé 0x7fcar dans utf-8, cela indique une partie d'un substitut. Cependant, decodeURIComponent("%C2%A9") //gives you ©Unescape ne fonctionnerait pas correctement sur cela // ©ET il ne générerait pas d'erreur, donc unescape peut conduire à un code bogué si vous ne connaissez pas vos données.

aamarks
la source
1

L'utilisation JSON.decodepour cela présente des inconvénients importants dont vous devez être conscient:

  • Vous devez mettre la chaîne entre guillemets
  • De nombreux caractères ne sont pas pris en charge et doivent eux-mêmes être échappés. Par exemple, en passant une des conditions suivantes à JSON.decode(après les envelopper dans des guillemets) sera erreur même si ceux - ci sont valides: \\n, \n, \\0,a"a
  • Il ne prend pas en charge les échappements hexadécimaux: \\x45
  • Il ne prend pas en charge les séquences de points de code Unicode: \\u{045}

Il y a également d'autres mises en garde. Essentiellement, utiliser JSON.decodeà cette fin est un hack et ne fonctionne pas comme vous pourriez toujours vous y attendre. Vous devez vous en tenir à l'utilisation de la JSONbibliothèque pour gérer JSON, pas pour les opérations de chaîne.


J'ai récemment rencontré ce problème moi-même et je voulais un décodeur robuste, alors j'ai fini par en écrire un moi-même. Il est complet et minutieusement testé et est disponible ici: https://github.com/iansan5653/unraw . Il imite le plus fidèlement possible la norme JavaScript.

Explication:

La source est d'environ 250 lignes, donc je ne vais pas tout inclure ici, mais elle utilise essentiellement le Regex suivant pour trouver toutes les séquences d'échappement, puis les analyse en utilisant parseInt(string, 16)pour décoder les nombres en base 16, puis String.fromCodePoint(number)pour obtenir le caractère correspondant:

/\\(?:(\\)|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

Commenté (REMARQUE: cette expression régulière correspond à toutes les séquences d'échappement, y compris les séquences non valides. Si la chaîne génère une erreur dans JS, elle renvoie une erreur dans ma bibliothèque [c'est-à-dire, une '\x!!'erreur]):

/
\\ # 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

Exemple

En utilisant cette bibliothèque:

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
Ian
la source