How to decode Chinese hex string into Chinese characters or JavaScript? - javascript

I am working on a Rails app.
I am using an API that returns some Chinese provinces.
The API returns the provinces in hex strings, for example:
{ "\xE5\x8C\x97\xE4\xBA\xAC" => "some data" }
My JavaScript calls a controller that returns this hash. I put all the province strings into a dropdown but the strings show up as a black diamond with a question mark in the middle. I am wondering how do I convert the Ruby hex string into actual Chinese characters, 北京? Or if possible, can I convert the hex string in JavaScript into Chinese characters?

The bytes \xE5\x8C\x97 are the UTF-8 representation of 北 and \xE4\xBA\xAC is the UTF-8 representation of 京. So this string:
"\xE5\x8C\x97\xE4\xBA\xAC"
is 北京 if the bytes are interpreted as UTF-8. That you're seeing hex codes instead of Chinese characters suggests that the string's encoding is binary:
> s = "\xE5\x8C\x97\xE4\xBA\xAC"
=> "北京"
> s.encoding
=> #<Encoding:UTF-8>
> s.force_encoding('binary')
=> "\xE5\x8C\x97\xE4\xBA\xAC"
So this API you're talking to is speaking UTF-8 but somewhere your application is losing track of what encoding that string is supposed to be. If you force the encoding to be UTF-8 then the problem goes away:
> s.force_encoding('utf-8')
=> "北京"
You should fix this encoding problem at the very edge of your application where it reads data from this remote API. Once that's done, everything should be sensible UTF-8 everywhere that you care about. This should fix your JavaScript problem as well as JavaScript is quite happy to work with UTF-8.

I think you can do like this: doc
rb:
2.1.2 :002 > require 'uri'
=> true
2.1.2 :003 > URI.decode("\xE5\x8C\x97\xE4\xBA\xAC")
=> "北京"
js: decodeURIComponent(URIstring)

Related

Decoding Base64 String in Java

I'm using Java and I have a Base64 encoded string that I wish to decode and then do some operations to transform.
The correct decoded value is obtained in JavaScript through function atob(), but in java, using Base64.decodeBase64() I cannot get an equal value.
Example:
For:
String str = "AAAAAAAAAAAAAAAAAAAAAMaR+ySCU0Yzq+AV9pNCCOI="
With JavaScript atob(str) I get ->
"Æ‘û$‚SF3«àö“Bâ"
With Java new String(Base64.decodeBase64(str)) I get ->
"Æ?û$?SF3«à§ö?â"
Another way I could fixed the issue is to run JavaScript in Java with a Nashorn engine, but I'm getting an error near the "$" symbol.
Current Code:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
String script2 = "function decoMemo(memoStr){ print(atob(memoStr).split('')" +
".map((aChar) => `0${aChar.charCodeAt(0).toString(16)}`" +
".slice(-2)).join('').toUpperCase());}";
try {
engine.eval(script2);
Invocable inv = (Invocable) engine;
String returnValue = (String)inv.invokeFunction("decoMemo", memoTest );
System.out.print("\n result: " + returnValue);
} catch (ScriptException | NoSuchMethodException e1) {
e1.printStackTrace();
Any help would be appreciated. I search a lot of places but can't find the correct answer.
btoa is broken and shouldn't be used.
The problem is, bytes aren't characters. Base64 encoding does only one thing. It converts bytes to a stream of characters that survive just about any text-based transport mechanism. And Base64 decoding does that one thing in reverse, it converts such characters into bytes.
And the confusion is, you're printing those bytes as if they are characters. They are not.
You end up with the exact same bytes, but javascript and java disagree on how you're supposed to turn that into an ersatz string because you're trying to print it to a console. That's a mistake - bytes aren't characters. Thus, some sort of charset encoding is being used, and you don't want any of this, because these characters clearly aren't intended to be printed like that.
Javascript sort of half-equates characters and bytes and will freely convert one to the other, picking some random encoding. Oof. Javascript sucks in this regard, it is what it is. The MDN docs on btoa explains why you shouldn't use it. You're running into that problem.
Not entirely sure how you fix it in javascript - but perhaps you don't need it. Java is decoding the bytes perfectly well, as is javascript, but javascript then turns those bytes into characters into some silly fashion and that's causing the problem.
What you have there is not a text string at all. The giveaway is the AA's at the beginning. Those map to a number of zero bytes. That doesn't translate to meaningful text in any standard character set.
So what you have there is most likely binary data. Converting it to a string is not going to give you meaningful text.
Now to explain the difference you are seeing between Java and Javascript. It looks to me as if both Java and Javascript are making a "best effort" attempt to convert the binary data as if is was encoded in ISO-8859-1 (aka ISO LATIN-1).
The problem is some of the bytes codes are mapping to unassigned codes.
In the Java case those unassigned codes are being mapped to ?, either when the string is created or when it is being output.
In the Javascript case, either the unassigned codes are not included in the string, or them are being removed when you attempt to display them.
For the record, this is how an online base64 decoder the above for me:
����������������Æû$SF3«àöBâ
The unassigned codes are 0x91 0x82 and 0x93. 0x15 and 0x0B are non-printing control codes.
But the bottom line is that you should not be converting this data into a string in either Java or in Javascript. It should be treated as binary; i.e. an array of byte values.
byte[] data = Base64.getDecoder().decode(str);

Decode utf8 character on javascript

I have a badly configured third party service that outputs strings like this:
"SK Uni=C4=8Dov vs Prostejov"
I want to replace on the fly all the wrong characters it sends me, so my modules work with the correctly decoded string
I have found on this website (https://www.compart.com/en/unicode/U+010D) that the =C4=8D substring corresponds to the utf-8 character č
https://www.compart.com/en/unicode/U+010D
č
...
UTF-8 Encoding: 0xC4 0x8D
UTF-16 Encoding: 0x010D
UTF-32 Encoding: 0x0000010D
...
but I cannot find the way to decode it automatically.
I've tried with:
>> String.fromCodePoint(0xc48d)
"쒍"
>> String.fromCodePoint("0xc4 0x8d")
RangeError
>> String.fromCharCode(0xc48d)
"쒍"
etc...
If I do it with the utf-16 code, String.fromCodePoint(0x010D) outputs the correct character.
How can I make it work with utf-8 instead of utf-16 codes?
Should I convert my string to utf16 achieve what I want? If so, How can I convert it?
Since the encoding is almost identical to percent escapes used in URLs, you can simply use:
decodeURIComponent("SK Uni=C4=8Dov vs Prostejov".replace(/=/g, "%"))

Javascript: convert CSV string into a) UTF-8 and b) a 2D array

Two questions in one, not sure if that's allowed, but they're directly related to the same code.
I retrieve a CSV string as a HTTP response in Javascript - this string comes in UTF-16 encoding it seems, as it has for example ' € ' instead of '€'.
a) How can I convert this to UTF-8 in vanilla Javascript?
Once that's done, how do I
b) transform the multi-line CSV into a 2D array in vanilla Javascript?
Thanks!
[UPDATE]
Based on anqooqie's pointers, I take the following approach to re-encode the string:
OK, clear - so to be honest, I went a slightly different way (as the reencode function didn't work for me and it threw a generic error code) and now do the below;
var O = new ActiveXObject('ADODB.Stream');
O.Type = 2;
O.Open;
O.Charset = 'ISO-8859-1';
O.LineSeparator = 10;
O.WriteText (csvStr);
O.Position = 0;
O.Charset = 'UTF-8';
And this works fine and in pretty much a split second (even though it's a 35K row CSV). Now if I want to put it back into the csvStr, I would do
csvStr = O.ReadText
but this takes ages - is that expected or am I doing something wrong?
For putting it into a 2D array, I split on the LineSeparator and then loop using a regex, which seems to work.
var A = new Array
A.push(csvStr[0].match(/"[^"]*"|[^,]+/g))
The vast delay on the readText is bothering me though, especially as the WriteText is so quick. Any help is appreciated.
Looks like you are confused about the terms of character encoding, so let's reconfirm that.
String is just a string.
There is no "UTF-16 string", nor "UTF-8 string".
Character encoding is a protocol which converts between a string and a byte array.
UTF-16 is one of the character encodings.
Also, both of UTF-8 and ISO-8859-1 are character encodings.
In UTF-16, the string '€' can be encoded to a byte array 20 AC.
In UTF-8, the string '€' can be encoded to a byte array E2 82 AC.
In ISO-8859-1, the byte array E2 82 AC can be decoded to a string 'â¬'.
Now, you may find that 'â¬' is not a "UTF-16 string".
It is '€' encoded as UTF-8 and mistakenly decoded as ISO-8859-1.
a) How can I convert this to UTF-8 in vanilla Javascript?
What you should do is to fix the code to retrieve a CSV file.
I cannot tell you how to fix it since I do not know your code, but I believe that it now decodes a CSV file as ISO-8859-1.
You should fix the character encoding from ISO-8859-1 to UTF-8.
If the code is not yours and you cannot fix it, you can use a workaround.
In other words, you can 1) re-encode a mistakenly decoded string as ISO-8859-1, and 2) re-decode it as UTF-8.
1)
// Note: This code requires ES5 or later.
function reencode(inputString) {
return Array.apply(null, Array(inputString.length)).map(function (x, i) { return inputString.charCodeAt(i); });
}
2)
See this answer.
b) How do I transform the multi-line CSV into a 2D array in vanilla Javascript?
See this answer.

Why are atob and btoa not reversible

I'm trying to find a simple way to record and temporarily obfuscate answers to "quiz" questions I'm writing in Markdown. (I'll tell the students the quiz answers during the presentation, so I'm not looking for any kind of secure encryption.)
I thought I could use atob('message I want to obfuscate') then tell students they can use btoa() in their developer tools panel to reverse the process. However the following does not return 'one':
btoa( atob('one') )
Does anyone know why this doesn't return 'one'? Are there other methods built into JavaScript that will allow one to loosely encrypt and decrypt a message? (I'm working with absolute beginners who might be confused by functions and who would be very confused trying to add libraries to a page).
That is the reason.
In Base64 encoding, the length of output encoded String must be a
multiple of 3. If it's not, the output will be padded with additional
pad characters (=). On decoding, these extra padding characters will
be discarded.
var string1 = "one",
string2 = "one2";
console.log("Value of string1", string1)
console.log("Decoded string1", atob(string1))
console.log("Encoded string1", btoa(atob(string1)))
console.log("-------------------------------------")
console.log("Value of string2", string2)
console.log("Decoded string2", atob(string2))
console.log("Encoded string2", btoa(atob(string2)))
As #george pointed out, one must use btoa() before using atob():
atob( btoa( 'hello' ) )
btoa means binary to ascii: input is Binary=any kind of data: text, images, audio. Output is Ascii=its base64 encoding, which is an ascii subset, i.e. a text string containing only upper and lowercase letters, numbers, comma, plus, slash, equal sign (only for padding at end).
atob means ascii to binary: input MUST be a subset of Ascii, i.e. the result of a base64 encoded string. Output is Binary=any type of data (text, image, audio, ...).

What is the best way to serialize a JavaScript object into something that can be used as a fragment identifier (url#hash)?

My page state can be described by a JavaScript object that can be serialized into JSON. But I don't think a JSON string is suitable for use in a fragment ID due to, for example, the spaces and double-quotes.
Would encoding the JSON string into a base64 string be sensible, or is there a better way? My goal is to allow the user to bookmark the page and then upon returning to that bookmark, have a piece of JavaScript read window.location.hash and change state accordingly.
I think you are on a good way. Let's write down the requirements:
The encoded string must be usable as hash, i.e. only letters and numbers.
The original value must be possible to restore, i.e. hashing (md5, sha1) is not an option.
It shouldn't be too long, to remain usable.
There should be an implementation in JavaScript, so it can be generated in the browser.
Base64 would be a great solution for that. Only problem: base64 also contains characters like - and +, so you win nothing compared to simply attaching a JSON string (which also would have to be URL encoded).
BUT: Luckily, theres a variant of base64 called base64url which is exactly what you need. It is specifically designed for the type of problem you're describing.
However, I was not able to find a JS implementation; maybe you have to write one youself – or do a bit more research than my half-assed 15 seconds scanning the first 5 Google results.
EDIT: On a second thought, I think you don't need to write an own implementation. Use a normal implementation, and simply replace the “forbidden” characters with something you find appropriate for your URLs.
Base64 is an excellent way to store binary data in text. It uses just 33% more characters/bytes than the original data and mostly uses 0-9, a-z, and A-Z. It also has three other characters that would need encoded to be stored in the URL, which are /, =, and +. If you simply used URL encoding, it would take up 300% (3x) the size.
If you're only storing the characters in the fragment of the URL, base64-encoded text it doesn't need to be re-encoded and will not change. But if you want to send the data as part of the actual URL to visit, then it matters.
As referenced by lxg, there there is a base64url variant for that. This is a modified version of base64 to replace unsafe characters to store in the URL. Here is how to encode it:
function tobase64url(s) {
return btoa(x).replace(/\+/g,'-').replace(/\//g,'_').replace(/=/g,'');
}
console.log(tobase64url('\x00\xff\xff\xf1\xf1\xf1\xff\xff\xfe'));
// Returns "AP__8fHx___-" instead of "AP//8fHx///+"
And to decode a base64 string from the URL:
function frombase64url(s) {
return atob(x.replace(/-/g,'+').replace(/_/g, '/'));
}
Use encodeURIComponent and decodeURIComponent to serialize data for the fragment (aka hash) part of the URL.
This is safe because the character set output by encodeURIComponent is a subset of the character set allowed in the fragment. Specifically, encodeURIComponent escapes all characters except:
A - Z
a - z
0 - 9
- . _ ~ ! ' ( ) *
So the output includes the above characters, plus escaped characters, which are % followed by hexadecimal digits.
The set of allowed characters in the fragment is:
A - Z
a - z
0 - 9
? / : # - . _ ~ ! $ & ' ( ) * + , ; =
percent-encoded characters (a % followed by hexadecimal digits)
This set of allowed characters includes all the characters output by encodeURIComponent, plus a few other characters.

Categories

Resources