Jak zdekodować ciąg z unikodowym kodem ucieczki?

89

Nie jestem pewien, jak to się nazywa, więc mam problemy z jego wyszukaniem. Jak zdekodować ciąg znaków Unicode od http\u00253A\u00252F\u00252Fexample.comdo za http://example.compomocą JavaScript? Próbowałem unescape, decodeURIi decodeURIComponenttak myślę, że jedyną rzeczą, w lewo jest ciąg zastąpić.

EDYCJA: Ciąg nie jest wpisywany, ale podciąg z innego fragmentu kodu. Aby rozwiązać problem, musisz zacząć od czegoś takiego:

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

Mam nadzieję, że to pokazuje, dlaczego unescape () nie działa.

styfle
źródło
Skąd pochodzi sznurek?
Cameron,
@Cameron: Ciąg pochodzi ze skryptu, do którego zadzwoniłem do innerHTML, aby go pobrać. Dlatego odpowiedź Alexa nie działa.
styfle

Odpowiedzi:

109

Edycja (2017-10-12) :

@MechaLynx i @ Kevin-Weber uwaga, która unescape()jest przestarzała ze środowisk innych niż przeglądarki i nie istnieje w TypeScript. decodeURIComponentjest zamiennikiem typu drop-in. Aby uzyskać szerszą zgodność, użyj zamiast tego poniższego:

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

Oryginalna odpowiedź:

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

Możesz odciążyć całą pracę JSON.parse

radicand
źródło
6
Ciekawy. Musiałem dodać dookoła cudzysłowy. unescape(JSON.parse('"' + s + '"'));Jaki jest powód tych dodatkowych cytatów? Czy to czyni go poprawnym JSON?
styfle
1
Zauważ, że wydaje się to być znacznie szybsze niż fromCharCodepodejście: jsperf.com/unicode-func-vs-json-parse
nrabinowitz
17
Ważna uwaga na temat odpowiedzi @ styfle: nie używaj zamiast tego, gdy masz do JSON.parse('"' + s + '"')czynienia z niezaufanymi danymi JSON.parse('"' + s.replace('"', '\\"') + '"'), w przeciwnym razie twój kod zostanie uszkodzony, gdy dane wejściowe będą zawierały cudzysłowy.
ntninja
7
Świetna odpowiedź @ alexander255, ale tak naprawdę chciałbyś użyć: JSON.parse ('"' + str.replace (/ \" / g, '\\ "' + '"'), aby zastąpić WSZYSTKIE wystąpienia tego znaku w całym ciąg znaków zamiast go zastępować
CS
2
Dla tych, którzy zetknęli się z tym i martwią się, ponieważ unescape()został wycofany, decodeURIComponent()działa identycznie jak unescape()w tym przypadku, więc po prostu zastąp go tym i jesteś dobry.
mechalynx
116

AKTUALIZACJA : Należy pamiętać, że jest to rozwiązanie, które powinno mieć zastosowanie do starszych przeglądarek lub platform innych niż przeglądarki i jest utrzymywane przy życiu do celów instruktażowych. Aby uzyskać bardziej aktualną odpowiedź, zapoznaj się z poniższą odpowiedzią @radicand.


To jest ciąg znaków ze znakami ucieczki Unicode. Najpierw ciąg został usunięty, a następnie zakodowany za pomocą Unicode. Aby przywrócić normalny tryb:

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

Aby wyjaśnić: szukam wyrażenia regularnego \u0025. Jednakże, ponieważ muszę tylko część tego łańcucha dla mojego zastąpienia pracy używam nawiasów wyizolować część Idę do ponownego wykorzystania 0025. Ta wyodrębniona część nazywana jest grupą.

giCzęść na końcu wyrazu oznacza powinien dopasować wszystkie instancje w ciągu, nie tylko pierwszy, i że dopasowanie powinno być przypadek niewrażliwe. Na przykładzie może to wyglądać niepotrzebnie, ale zwiększa wszechstronność.

Teraz, aby dokonać konwersji z jednego ciągu na drugi, muszę wykonać kilka kroków na każdej grupie każdego dopasowania, a nie mogę tego zrobić, po prostu przekształcając ciąg. Pomocne jest to, że operacja String.replace może przyjąć funkcję, która zostanie wykonana dla każdego dopasowania. Zwrócenie tej funkcji spowoduje zastąpienie samego dopasowania w ciągu.

Używam drugiego parametru, który akceptuje ta funkcja, czyli grupy, której potrzebuję, i przekształcam ją na równoważną sekwencję utf-8, a następnie używam wbudowanej unescapefunkcji do dekodowania ciągu do jego właściwej postaci.

Ioannis Karadimas
źródło
3
Dzięki. Czy mógłbyś trochę wyjaśnić, co robisz? Wygląda na to, że wyrażenie regularne szuka \uprefiksu, a następnie 4-znakowej liczby szesnastkowej (litery lub cyfry). Jak działa funkcja w metodzie replace?
styfle
1
Masz rację, to wymagało wyjaśnienia, więc zaktualizowałem swój post. Cieszyć się!
Ioannis Karadimas
1
Świetne rozwiązanie. W moim przypadku koduję wszystkie znaki międzynarodowe (inne niż ASCII) wysyłane z serwera jako unikod ucieczki, a następnie używam funkcji w przeglądarce do dekodowania tych znaków na prawidłowe znaki UTF-8. Okazało się, że miałem zaktualizować następujące regex w kolejności do znaków połowach ze wszystkich języków (czyli tajski):var r = /\\u([\d\w]{1,})/gi;
Nathan Hanna
2
Zauważ, że wydaje się, że jest to znacznie wolniejsze niż JSON.parsepodejście: jsperf.com/unicode-func-vs-json-parse
nrabinowitz
1
@IoannisKaradimas Z pewnością istnieje coś takiego jak wycofanie w JavaScript. Stwierdzenie tego, a następnie poparcie tego przez stwierdzenie, że starsze przeglądarki zawsze muszą być obsługiwane, to perspektywa całkowicie ahistoryczna. W każdym razie każdy, kto chce z tego skorzystać, a także chce tego uniknąć, unescape()może decodeURIComponent()zamiast tego użyć . W tym przypadku działa identycznie. Poleciłbym jednak podejście radicand, ponieważ jest prostsze, tak samo obsługiwane i szybsze w wykonaniu, z tymi samymi wynikami (pamiętaj jednak o przeczytaniu komentarzy).
mechalynx
21

Zauważ, że użycie unescape()jest przestarzałe i nie działa na przykład z kompilatorem TypeScript.

Na podstawie odpowiedzi Radicand i sekcji komentarzy poniżej, oto zaktualizowane rozwiązanie:

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

http://example.com

Kevin Weber
źródło
To nie działa w przypadku niektórych ciągów, ponieważ cudzysłowy mogą zepsuć ciąg JSON i spowodować błędy podczas analizowania JSON. W takich przypadkach użyłem drugiej odpowiedzi ( stackoverflow.com/a/7885499/249327 ).
nickdos
2

Nie mam wystarczającej liczby przedstawicieli, aby umieścić to pod komentarzami do istniejących odpowiedzi:

unescapejest przestarzały tylko w przypadku pracy z identyfikatorami URI (lub jakimkolwiek zakodowanym utf-8), co prawdopodobnie ma miejsce w przypadku większości ludzi. encodeURIComponentkonwertuje ciąg znaków js na UTF-8 ze ucieczką i decodeURIComponentdziała tylko na bajtach UTF-8 ze ucieczką. Zgłasza błąd dla czegoś takiego jak, decodeURIComponent('%a9'); // errorponieważ rozszerzone ascii nie jest poprawnym utf-8 (mimo że jest to nadal wartość Unicode), podczas gdyunescape('%a9'); // © musisz znać swoje dane, gdy używasz decodeURIComponent.

decodeURIComponent nie będzie działał "%C2"ani żaden pojedynczy bajt, 0x7fponieważ w utf-8 wskazuje część surogatu. Jednak decodeURIComponent("%C2%A9") //gives you ©Unescape nie działałby prawidłowo na tym // ©ORAZ nie zgłosiłby błędu, więc unescape może prowadzić do błędnego kodu, jeśli nie znasz swoich danych.

aamarks
źródło
1

Używanie JSON.decodedo tego ma istotne wady, o których musisz wiedzieć:

  • Musisz zawinąć ciąg w podwójne cudzysłowy
  • Wiele postaci nie jest obsługiwanych i same muszą przed nimi uciec. Na przykład, przekazując dowolną z następujących czynności JSON.decode(po owijanie ich w cudzysłów) będzie błędu, mimo wszystko to są ważne: \\n, \n, \\0,a"a
  • Nie obsługuje znaków szesnastkowych: \\x45
  • Nie obsługuje sekwencji punktów kodowych Unicode: \\u{045}

Istnieją również inne zastrzeżenia. Zasadniczo używanie JSON.decodedo tego celu jest hackowaniem i nie działa tak, jak można się zawsze spodziewać. Należy trzymać się JSONbiblioteki do obsługi formatu JSON, a nie operacji na łańcuchach.


Niedawno napotkałem ten problem i chciałem solidnego dekodera, więc napisałem go sam. Jest kompletny, dokładnie przetestowany i dostępny tutaj: https://github.com/iansan5653/unraw . Naśladuje standard JavaScript tak dokładnie, jak to możliwe.

Wyjaśnienie:

Źródło ma około 250 linii, więc nie będę go tutaj umieszczać, ale zasadniczo używa następującego Regex, aby znaleźć wszystkie sekwencje specjalne, a następnie analizuje je, używając parseInt(string, 16)do dekodowania liczb o podstawie 16, a następnie String.fromCodePoint(number)do uzyskania odpowiedniego znaku:

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

Skomentowano (UWAGA: to wyrażenie regularne pasuje do wszystkich sekwencji ucieczki, w tym nieprawidłowych. Jeśli ciąg spowodowałby błąd w JS, zgłasza błąd w mojej bibliotece [tj. '\x!!'Spowoduje błąd]):

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

Przykład

Korzystanie z tej biblioteki:

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
źródło