parsing rfc2184 - compliant utf-8 text

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

parsing rfc2184 - compliant utf-8 text

jtuchel

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:
<img src="data:image/png;base64,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" alt="" data-iml="512786">

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/70b92c3d-6e1a-4fe0-bb67-c236b8336abfn%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

jtuchel
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/9b58b3cc-9ab7-4ccd-b807-c76cdd49d15an%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

jtuchel
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/aaca157c-00ac-4ddf-a00f-32a168bea1e8n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

jtuchel
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/d4d46662-afa2-4ae5-81c1-b4cd43d18268n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

Hans-Martin Mosner-3
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/6de1463c-0897-4116-ac33-6a22534682b0n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

jtuchel
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/188b7992-ff7c-4701-bc6e-6af3c0ea4996n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

Seth Berman
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/78ec9ecf-3993-4ade-b4ce-c408d755954an%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

jtuchel
HI Seth,


I am glad you're taking this Unicode thing very serious. It is a minefield that seems neverending ;-) Whenever I think I have finally solved all issues, some new esoteric case comes around the corner. So it is good you take your time to make things work. You think you've got UTF-8 conversion working, but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first.... You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!

So even if I'd love to have THE solution in my hands rather sooner than later, I prefer a working solution over one that's only half baked today ;-)

I'm looking forward to your updates on this.

Joachim








Seth Berman schrieb am Sonntag, 6. Dezember 2020 um 15:47:03 UTC+1:
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/96669537-b67d-4943-bf76-d608638d8421n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

Seth Berman
Hi Joachim,

"... but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first"
- In our UnicodeString, you don't typically have to think about normalization...unless you wish too.  The apis for NFC, NFD, NFKC, NFKD are there if you want them.
A new UnicodeString is logically composed of extended grapheme clusters.  Other ways to express that are "user-perceived character" or "those things your cursor hops over as you press the left and right key".
=, <, hash and so on with our Grapheme will ensure a common normalized form under the hood before performing the operation.

"...You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!"
I just want to make sure I understand.  Is your expectation that a UnicodeString can be reified from data for which the encoding (i.e. UTF-8, UTF-16LE...) is not known?
Something like this? https://unicodebook.readthedocs.io/guess_encoding.html
I suppose you could attempt to first create from utf8, then utf16le, then utf16be and wait for one that works...but an encoding-guesser wasn't something that was going to be part of this.
Or when you say "encoding"...are you referring to normalization?

- Seth
On Monday, December 7, 2020 at 3:59:55 AM UTC-5 [hidden email] wrote:
HI Seth,


I am glad you're taking this Unicode thing very serious. It is a minefield that seems neverending ;-) Whenever I think I have finally solved all issues, some new esoteric case comes around the corner. So it is good you take your time to make things work. You think you've got UTF-8 conversion working, but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first.... You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!

So even if I'd love to have THE solution in my hands rather sooner than later, I prefer a working solution over one that's only half baked today ;-)

I'm looking forward to your updates on this.

Joachim








Seth Berman schrieb am Sonntag, 6. Dezember 2020 um 15:47:03 UTC+1:
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/ada09cea-630b-4bc6-b0e6-f0c1515b2574n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

jtuchel
Hi Seth,

sorry for my late reply. I'm having crazy days, but who doesn't ?


Seth Berman schrieb am Montag, 7. Dezember 2020 um 14:43:41 UTC+1:
Hi Joachim,

"... but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first"
 
- In our UnicodeString, you don't typically have to think about normalization...unless you wish too.  The apis for NFC, NFD, NFKC, NFKD are there if you want them.
A new UnicodeString is logically composed of extended grapheme clusters.  Other ways to express that are "user-perceived character" or "those things your cursor hops over as you press the left and right key".
=, <, hash and so on with our Grapheme will ensure a common normalized form under the hood before performing the operation.

Well, I guess you are talking about a class that will be available in VAST 2021?
When all you have is iconv() or better #convertFromCodePage:, you have to deal with normalization. I only learned this year that an ü is not necessarily an ü. We could handle file uploads of files with names including German umlauts just fine. Until our first Mac user came along and uploaded a file where the ü was encoded as a sequence of the two dots and an u. iconv() doesn't work with these. In the end the words 'überweisung" and 'überweisung' were not the same any more... it took some while to understand.
This is not a VAST bug or anything, just a consequence of not having proper unicode support in your IDE. Soon this will be over. Unfortunately, not with 2021.


 
"...You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!"
I just want to make sure I understand.  Is your expectation that a UnicodeString can be reified from data for which the encoding (i.e. UTF-8, UTF-16LE...) is not known?

Exactly. This is why I asked for some LibICU functions in VASt some time ago which help with guessing whether a String can be assumed as UTF-8 or UTF-16 or ISO-8859. 
This is quite important, because if you send #convertFromCodepage: 'UTF-8' to a String which has already been converted, you risk getting an error code 84 from iconv(). iconv does not check whether the conversion makes sense or not, it simply fails. Which is okay, you just have to deal with it.
So the best thing to do is to try and call convertFromCodePage: a few times inside an exception handler and see if one of the results looks similar to what you'd like to get. So I ended up with a method that tries to guess whether a String is in UTF-8 by converting it from utf-8 to the local codepage and back and if the end result looks like the first string, it seems to be utf-8. If I get an exception on the way, I guess it is already in the local codepage (ISO-8859-1 in my case). It would be good to add some BOM checks and such to this, so that you can already tell if the String is UTF-8 or -16 before calling iconv twice, but then there still are Strings that do not come with a BOM, so I didn't go that extra mile.

Again, I only mention this to explain why I am eagerly waiting for libicu or something equivalent, not to be offensive about VAST.
 
I suppose you could attempt to first create from utf8, then utf16le, then utf16be and wait for one that works...but an encoding-guesser wasn't something that was going to be part of this.

Well, I don't really care if guessing and conversion are combined in a single call. If I can just say: 'Make this ISO-8859-1, no matter what it is now', I am also happy. I'm not keen on doing this on my own all the time.
AFAIU, libicu cannot do that. So I'm afraid a guesser is something that will be needed pretty soon after the conversion is done.
 
Or when you say "encoding"...are you referring to normalization?

You know, I actually don't know. In my naive picture, if I want a String converted from UTF-8 to the local codepage, I don't really care how an ü is encoded in the UTF-8 String. I want my ü ;-) OTOH, if I send the String back to a web browser (e.g. in a web app) I have to send it in UTF-8, and it seems there is no way I can ignore that. If I receive an Ü in one normalization scheme from the browser and send it back in another one, I am asking for trouble as soon as there is, say, Javascript code running on the client/Browser that compares the String or such.

I know this is not something that can be solved easily. I'd have to keep track of what normalization scheme the ü was encoded with when I received it from the Browser and when I send it back use that info to re-apply that scheme before sending the String down.
With only iconv() I have no chance to even find out.

So I guess what is needed is a guesser for code page and normalization. Or - at least as likely - I am missing some important information.

The best possible option, however, would be a Smalltalk dialect that speaks UTF-8 natively. I create a String and don't care at all. I guess this is going to remain a dream for a while... ;-)


Joachim


 

- Seth
On Monday, December 7, 2020 at 3:59:55 AM UTC-5 jtu...@... wrote:
HI Seth,


I am glad you're taking this Unicode thing very serious. It is a minefield that seems neverending ;-) Whenever I think I have finally solved all issues, some new esoteric case comes around the corner. So it is good you take your time to make things work. You think you've got UTF-8 conversion working, but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first.... You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!

So even if I'd love to have THE solution in my hands rather sooner than later, I prefer a working solution over one that's only half baked today ;-)

I'm looking forward to your updates on this.

Joachim








Seth Berman schrieb am Sonntag, 6. Dezember 2020 um 15:47:03 UTC+1:
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/4b89ac77-ed90-4076-8ace-f86bf77098c8n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

Seth Berman
Hello Joachim,

Thanks for your thoughts on this, much appreciated.
If I were to sum up your points, I would put them at "handling normalization for me" and "Guess the character encoding of an input string"

"Handling normalization for me"
I'll be honest with you, if having this abstracted away was the measure of "proper Unicode support", then most languages today would fail.
This is one of the reasons that I have chosen to have the basic unit of a UnicodeString at the grapheme level as opposed to answering Unicode scalars by default.
The other major reason is to properly deal with the plethora of API's which a UnicodeString is going to have to support as a result of being way down the
Collection hierarchy.  Take something simple like 'reverse'.  Try doing that with an NFD form of 'ü' and see what you get...you won't like it.

Most languages...even the so-called ones with "Unicode support" (which by the way is a near meaningless phrase) make you deal with normalization as just part of what you do.
For example, making sure that things are in NFC (or NFD) before you do these kinds of things is always on you to deal with.

I have studied most language's implementations of Unicode at this point and believe Swift and Perl 6 (now Raku) made the appropriate decisions with how they are representing this.
Its certainly far more complexity hoisted onto the implementer to do this, but anything lower level just leads us back to the same conversations regarding normalization.

With what has been developed, 'überweisung' at: 1 in your example answers the grapheme 'ü'.  It may be in NFC or NFD...it doesn't really matter for operations like '=' and 'hash'
and '<'.  However, we do maintain the original normalization under the hood because, for example, certain filesystems may require the name to be in some
normalized form or it won't find it.

The bottom line in this issue is that you will be dealing with graphemes (@ user-perceived characters) by default.  You won't care about normalization until you have some sort of
scenario where ensuring a normalized form is important.  And when you do, its easy.  We have asNFC, asNFD, asNFKC, asNFKD.  And you have the option to do it in-place or make a copy
or even just to test if your unicode string is in some normalized form.

If you want to work with something else, we have the analog of swift "views".  For us, these are bi-directional streams that subclass Stream and work on UnicodeString, Grapheme, and UnicodeScalar
You can do the normal next, atEnd, do: inject:into: and I'll be adding string slicing equivalents.
Right now the views are graphemes, unicodeScalars, utf8, utf16, utf32.
These views keep internal bookmarks to the bytes of the underlying implementation (which have nothing to do with how it is being presented to the user) so you get efficient O(n) streaming.

1 byte = 1 char = 1 user-perceived character is absolutely false, and trying to maintain that illusion is what got everybody into that codepage mess in the pre-unicode era.
And, it continues to get people into messes even during the unicode era because 1 Unicode codepoint = 1 character is also absolutely false....as you have found out.

"Guess the character encoding of an input string
I think if you do some research on this you'll see that, in general, you can't do this in a way that is 100% guaranteed to work.
Anybody that attempts to do this has to provide a confidence level of sorts and you even may have to provide a sufficient amount of input for it to even work.
We implement our Unicode in rust, so if I find a rust 'crate' that exposes this capability, I'll certainly look at wrapping it and would be happy to do so.
You can separately look at wrapping libICUs capability for it: http://userguide.icu-project.org/conversion/detection

As said previously, I plan to do a post to show some examples of where we are at that should be interesting.

Here is a small example.  I tend to use emojis because they are complex under the hood.
Part of this effort is to also ensure that workspaces and inspectors have scintilla editors in UTF-8 mode,
which is why its displaying the emoji and not gibberish.
I used this as my first test case to here https://hsivonen.fi/string-length/
The first screenshot is the emoji as  UnicodeString and it matches up with the website.
The second screenshot is the first of five unicodeScalars that make up the emoji.

<img src="data:image/png;base64,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" alt="" data-iml="6703175.985000009"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAtcAAAH2CAYAAAC7lqBTAAAgAElEQVR4Aey9f7Ak11XnmX/oD+8fjujxH4QihmCt2d1aa2d31mKWHbXGbHjaESA3+3ZlKTxoBGt30/i5rTaSPPBMGzH2w5ahWWuin7EWNUb4dYMNjW2teujl0QY0aoGRXxAv2loCS800Nq3B9gikxY2HMRJjOBvn5v3eOvfmzazMqqx6VfW+LyL75o97zz33m7cyP3n6VFZx3333CZfJNFhdXXUa3nHHHfLWt75VfuAHfkDe9ra3ydvf/na5++675dixY/JDP/RDcs8997jl3nvvFSzUfjLtqd/e1g+fvdtuu02+8zu/U/gZ3NvzgdeDvXH+8bk/cuSIu7/qPfZ7vud73Of/zW9+s9x5551y1113yfd///fLW97yFndfPnTokGA5fPiwcKEG05wDhV6M+EcFqAAVoAJUgApQASpABajAZAooVxOuJ9OQrakAFaACVIAKUAEqQAWogFOAcM2JQAWoABWgAlSAClABKkAFelKAcN2TkDRDBagAFaACVIAKUAEqQAUI15wDVIAKUAEqQAWoABWgAlSgJwUI1z0JSTNUgApQASpABagAFaACVIBwzTlABagAFaACVIAKUAEqQAV6UoBw3ZOQNEMFqAAVoAJUgApQASpABQjXnANUgApQASpABagAFaACVKAnBSaC61849UF53/veKT/1/rfJ//mBH4yWnvyjGSpABagAFaACVIAKUAEqsDAKjAXXt9z838mJB94lv/c7n5HPfvbfyud3npA/fLpc/uDS4/K5zz4mP/aety2MCHSUClABKkAFqAAVoAJUgAr0ocBYcH3ig/9SHv/MJ+Xzn/8D+epXX5C/+Itr8pd/+ZduuXbta/LlL1+VJ58834d/tEEFqAAVoAJUgApQASpABRZGgc5w/cjPfkA++ztbcunS0/LVr74o/+k/fUP++q//urI8//yL8sS/PS/vec+hhRGDjlIBKkAFqAAVoAJUYBkUKIpCuiyLPGYd587OzsghaB2tO+2/znD9nuOH5PHHPynPPfdFB9bf+EYerjWS/fmdz8infvlD8gu/8PFO47iycUt2QtyycaWTna6Vt1YLKVa3ujabm/pV/6/Ixi3mw3XLhmQV3FqVorhF2sirfYw6D86PYlXqlHTnd6TOpe/Zaupv3VgyZ2OU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KrDMChCul/nscmxUgApQASpABagAFaACM1WAcD1TudkZFaACVIAKUAEqQAWowDIrQLhe5rPLsVEBKkAFqAAVoAJUgArMVAHC9UzlZmdUgApQASpABagAFaACy6wA4XqZzy7HRgWoABWgAlSAClABKjBTBQjXM5WbnVEBKkAFqAAVoAJUgAosswKE62U+uxwbFaACVIAKUAEqQAWowEwVIFzPVG52RgWoABWgAlSAClABKrDMChCul/nscmxUgApQASpABagAFaACM1WAcD1TudkZFaACVIAKUAEqQAWowDIrQLhe5rPLsVEBKkAFqAAVoAJUgArMVAHC9UzlZmdUgApQASpABagAFaACy6wA4XqZzy7HRgWoABWgAlSAClABKjBTBQjXM5WbnVEBKkAFqAAVoAJUgAosswKE62U+uxwbFaACVIAKUAEqQAWowEwVIFzPVG52RgWoABWgAlSAClABKrDMChw+fFgKJexRf1urhRRFvKxula3KY6viN0W395+8MsrkxMevnNxf8ako9ktT164NHBdxvhbF0PeJnfIGnCamH2u36Zitl1tPtc7VsfvanIs2dazNaa2XYzNzbP9J6WsWddVtWmOkXSpABagAFaACVGC5FWgdue4CYF3qTiJvCsptbI1ss7UqRR9Qp3ay0L4lq0UhNdzdZgid6rQ5F23qdOq0c+VSk1R39Suv4YgO+jqHI7rhYSpABagAFaACVIAKpAoQrlNFegOzGoiuhe7UkX6224Bzmzr9eJO34iC65oHGHev6JNLbOcz7y71UgApQASpABagAFahToBe4TuEs3dbO4xSO5tSNOmfT/SOj0CHtw6ca7D8pW5pKYmDN+upAzqa+mHpp3222nb3ERrTvyknZb/uLAPOKnNyvEW4f1fVRcOuv86HRRpn2oik6kf5RP8M6dkxR/TTVxj0gIH1j0nNZ8xACZ9z4hn1g/JF/ZjxOX6up1x/tYFbLyIa2ic4V9C/LMiVq6Iez06sO1jOuUwEqQAWoABWgAouqwEzg2kGMASDpKXrr7EZAFJ8GB1qVfmOIqkBXn1HPyjgBbKWfV06umvxwD3FhPOX2/v22ThWEm20gp9zmwKf95Gzuj1Nj7DgS2JWtk2YMsf6ttqztbIMYvgHPw5z+6njc/LLn3T9kDdsArG2ufWrHb5sHi2g+9a1DduzcSQWoABWgAlSA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" alt="" data-iml="6940119.899999991">
- Seth

On Friday, December 18, 2020 at 8:09:22 AM UTC-5 [hidden email] wrote:
Hi Seth,

sorry for my late reply. I'm having crazy days, but who doesn't ?


Seth Berman schrieb am Montag, 7. Dezember 2020 um 14:43:41 UTC+1:
Hi Joachim,

"... but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first"
 
- In our UnicodeString, you don't typically have to think about normalization...unless you wish too.  The apis for NFC, NFD, NFKC, NFKD are there if you want them.
A new UnicodeString is logically composed of extended grapheme clusters.  Other ways to express that are "user-perceived character" or "those things your cursor hops over as you press the left and right key".
=, <, hash and so on with our Grapheme will ensure a common normalized form under the hood before performing the operation.

Well, I guess you are talking about a class that will be available in VAST 2021?
When all you have is iconv() or better #convertFromCodePage:, you have to deal with normalization. I only learned this year that an ü is not necessarily an ü. We could handle file uploads of files with names including German umlauts just fine. Until our first Mac user came along and uploaded a file where the ü was encoded as a sequence of the two dots and an u. iconv() doesn't work with these. In the end the words 'überweisung" and 'überweisung' were not the same any more... it took some while to understand.
This is not a VAST bug or anything, just a consequence of not having proper unicode support in your IDE. Soon this will be over. Unfortunately, not with 2021.


 
"...You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!"
I just want to make sure I understand.  Is your expectation that a UnicodeString can be reified from data for which the encoding (i.e. UTF-8, UTF-16LE...) is not known?

Exactly. This is why I asked for some LibICU functions in VASt some time ago which help with guessing whether a String can be assumed as UTF-8 or UTF-16 or ISO-8859. 
This is quite important, because if you send #convertFromCodepage: 'UTF-8' to a String which has already been converted, you risk getting an error code 84 from iconv(). iconv does not check whether the conversion makes sense or not, it simply fails. Which is okay, you just have to deal with it.
So the best thing to do is to try and call convertFromCodePage: a few times inside an exception handler and see if one of the results looks similar to what you'd like to get. So I ended up with a method that tries to guess whether a String is in UTF-8 by converting it from utf-8 to the local codepage and back and if the end result looks like the first string, it seems to be utf-8. If I get an exception on the way, I guess it is already in the local codepage (ISO-8859-1 in my case). It would be good to add some BOM checks and such to this, so that you can already tell if the String is UTF-8 or -16 before calling iconv twice, but then there still are Strings that do not come with a BOM, so I didn't go that extra mile.

Again, I only mention this to explain why I am eagerly waiting for libicu or something equivalent, not to be offensive about VAST.
 
I suppose you could attempt to first create from utf8, then utf16le, then utf16be and wait for one that works...but an encoding-guesser wasn't something that was going to be part of this.

Well, I don't really care if guessing and conversion are combined in a single call. If I can just say: 'Make this ISO-8859-1, no matter what it is now', I am also happy. I'm not keen on doing this on my own all the time.
AFAIU, libicu cannot do that. So I'm afraid a guesser is something that will be needed pretty soon after the conversion is done.
 
Or when you say "encoding"...are you referring to normalization?

You know, I actually don't know. In my naive picture, if I want a String converted from UTF-8 to the local codepage, I don't really care how an ü is encoded in the UTF-8 String. I want my ü ;-) OTOH, if I send the String back to a web browser (e.g. in a web app) I have to send it in UTF-8, and it seems there is no way I can ignore that. If I receive an Ü in one normalization scheme from the browser and send it back in another one, I am asking for trouble as soon as there is, say, Javascript code running on the client/Browser that compares the String or such.

I know this is not something that can be solved easily. I'd have to keep track of what normalization scheme the ü was encoded with when I received it from the Browser and when I send it back use that info to re-apply that scheme before sending the String down.
With only iconv() I have no chance to even find out.

So I guess what is needed is a guesser for code page and normalization. Or - at least as likely - I am missing some important information.

The best possible option, however, would be a Smalltalk dialect that speaks UTF-8 natively. I create a String and don't care at all. I guess this is going to remain a dream for a while... ;-)


Joachim


 

- Seth
On Monday, December 7, 2020 at 3:59:55 AM UTC-5 [hidden email] wrote:
HI Seth,


I am glad you're taking this Unicode thing very serious. It is a minefield that seems neverending ;-) Whenever I think I have finally solved all issues, some new esoteric case comes around the corner. So it is good you take your time to make things work. You think you've got UTF-8 conversion working, but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first.... You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!

So even if I'd love to have THE solution in my hands rather sooner than later, I prefer a working solution over one that's only half baked today ;-)

I'm looking forward to your updates on this.

Joachim








Seth Berman schrieb am Sonntag, 6. Dezember 2020 um 15:47:03 UTC+1:
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/d6c03c25-fe46-4bfd-b110-625577711472n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

Seth Berman
Not sure if the pictures showed from the last message, so I'll try attaching a screenshot
unicodeScreenshot.png

On Friday, December 18, 2020 at 10:21:58 AM UTC-5 Seth Berman wrote:
Hello Joachim,

Thanks for your thoughts on this, much appreciated.
If I were to sum up your points, I would put them at "handling normalization for me" and "Guess the character encoding of an input string"

"Handling normalization for me"
I'll be honest with you, if having this abstracted away was the measure of "proper Unicode support", then most languages today would fail.
This is one of the reasons that I have chosen to have the basic unit of a UnicodeString at the grapheme level as opposed to answering Unicode scalars by default.
The other major reason is to properly deal with the plethora of API's which a UnicodeString is going to have to support as a result of being way down the
Collection hierarchy.  Take something simple like 'reverse'.  Try doing that with an NFD form of 'ü' and see what you get...you won't like it.

Most languages...even the so-called ones with "Unicode support" (which by the way is a near meaningless phrase) make you deal with normalization as just part of what you do.
For example, making sure that things are in NFC (or NFD) before you do these kinds of things is always on you to deal with.

I have studied most language's implementations of Unicode at this point and believe Swift and Perl 6 (now Raku) made the appropriate decisions with how they are representing this.
Its certainly far more complexity hoisted onto the implementer to do this, but anything lower level just leads us back to the same conversations regarding normalization.

With what has been developed, 'überweisung' at: 1 in your example answers the grapheme 'ü'.  It may be in NFC or NFD...it doesn't really matter for operations like '=' and 'hash'
and '<'.  However, we do maintain the original normalization under the hood because, for example, certain filesystems may require the name to be in some
normalized form or it won't find it.

The bottom line in this issue is that you will be dealing with graphemes (@ user-perceived characters) by default.  You won't care about normalization until you have some sort of
scenario where ensuring a normalized form is important.  And when you do, its easy.  We have asNFC, asNFD, asNFKC, asNFKD.  And you have the option to do it in-place or make a copy
or even just to test if your unicode string is in some normalized form.

If you want to work with something else, we have the analog of swift "views".  For us, these are bi-directional streams that subclass Stream and work on UnicodeString, Grapheme, and UnicodeScalar
You can do the normal next, atEnd, do: inject:into: and I'll be adding string slicing equivalents.
Right now the views are graphemes, unicodeScalars, utf8, utf16, utf32.
These views keep internal bookmarks to the bytes of the underlying implementation (which have nothing to do with how it is being presented to the user) so you get efficient O(n) streaming.

1 byte = 1 char = 1 user-perceived character is absolutely false, and trying to maintain that illusion is what got everybody into that codepage mess in the pre-unicode era.
And, it continues to get people into messes even during the unicode era because 1 Unicode codepoint = 1 character is also absolutely false....as you have found out.

"Guess the character encoding of an input string
I think if you do some research on this you'll see that, in general, you can't do this in a way that is 100% guaranteed to work.
Anybody that attempts to do this has to provide a confidence level of sorts and you even may have to provide a sufficient amount of input for it to even work.
We implement our Unicode in rust, so if I find a rust 'crate' that exposes this capability, I'll certainly look at wrapping it and would be happy to do so.
You can separately look at wrapping libICUs capability for it: http://userguide.icu-project.org/conversion/detection

As said previously, I plan to do a post to show some examples of where we are at that should be interesting.

Here is a small example.  I tend to use emojis because they are complex under the hood.
Part of this effort is to also ensure that workspaces and inspectors have scintilla editors in UTF-8 mode,
which is why its displaying the emoji and not gibberish.
I used this as my first test case to here https://hsivonen.fi/string-length/
The first screenshot is the emoji as  UnicodeString and it matches up with the website.
The second screenshot is the first of five unicodeScalars that make up the emoji.


- Seth

On Friday, December 18, 2020 at 8:09:22 AM UTC-5 [hidden email] wrote:
Hi Seth,

sorry for my late reply. I'm having crazy days, but who doesn't ?


Seth Berman schrieb am Montag, 7. Dezember 2020 um 14:43:41 UTC+1:
Hi Joachim,

"... but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first"
 
- In our UnicodeString, you don't typically have to think about normalization...unless you wish too.  The apis for NFC, NFD, NFKC, NFKD are there if you want them.
A new UnicodeString is logically composed of extended grapheme clusters.  Other ways to express that are "user-perceived character" or "those things your cursor hops over as you press the left and right key".
=, <, hash and so on with our Grapheme will ensure a common normalized form under the hood before performing the operation.

Well, I guess you are talking about a class that will be available in VAST 2021?
When all you have is iconv() or better #convertFromCodePage:, you have to deal with normalization. I only learned this year that an ü is not necessarily an ü. We could handle file uploads of files with names including German umlauts just fine. Until our first Mac user came along and uploaded a file where the ü was encoded as a sequence of the two dots and an u. iconv() doesn't work with these. In the end the words 'überweisung" and 'überweisung' were not the same any more... it took some while to understand.
This is not a VAST bug or anything, just a consequence of not having proper unicode support in your IDE. Soon this will be over. Unfortunately, not with 2021.


 
"...You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!"
I just want to make sure I understand.  Is your expectation that a UnicodeString can be reified from data for which the encoding (i.e. UTF-8, UTF-16LE...) is not known?

Exactly. This is why I asked for some LibICU functions in VASt some time ago which help with guessing whether a String can be assumed as UTF-8 or UTF-16 or ISO-8859. 
This is quite important, because if you send #convertFromCodepage: 'UTF-8' to a String which has already been converted, you risk getting an error code 84 from iconv(). iconv does not check whether the conversion makes sense or not, it simply fails. Which is okay, you just have to deal with it.
So the best thing to do is to try and call convertFromCodePage: a few times inside an exception handler and see if one of the results looks similar to what you'd like to get. So I ended up with a method that tries to guess whether a String is in UTF-8 by converting it from utf-8 to the local codepage and back and if the end result looks like the first string, it seems to be utf-8. If I get an exception on the way, I guess it is already in the local codepage (ISO-8859-1 in my case). It would be good to add some BOM checks and such to this, so that you can already tell if the String is UTF-8 or -16 before calling iconv twice, but then there still are Strings that do not come with a BOM, so I didn't go that extra mile.

Again, I only mention this to explain why I am eagerly waiting for libicu or something equivalent, not to be offensive about VAST.
 
I suppose you could attempt to first create from utf8, then utf16le, then utf16be and wait for one that works...but an encoding-guesser wasn't something that was going to be part of this.

Well, I don't really care if guessing and conversion are combined in a single call. If I can just say: 'Make this ISO-8859-1, no matter what it is now', I am also happy. I'm not keen on doing this on my own all the time.
AFAIU, libicu cannot do that. So I'm afraid a guesser is something that will be needed pretty soon after the conversion is done.
 
Or when you say "encoding"...are you referring to normalization?

You know, I actually don't know. In my naive picture, if I want a String converted from UTF-8 to the local codepage, I don't really care how an ü is encoded in the UTF-8 String. I want my ü ;-) OTOH, if I send the String back to a web browser (e.g. in a web app) I have to send it in UTF-8, and it seems there is no way I can ignore that. If I receive an Ü in one normalization scheme from the browser and send it back in another one, I am asking for trouble as soon as there is, say, Javascript code running on the client/Browser that compares the String or such.

I know this is not something that can be solved easily. I'd have to keep track of what normalization scheme the ü was encoded with when I received it from the Browser and when I send it back use that info to re-apply that scheme before sending the String down.
With only iconv() I have no chance to even find out.

So I guess what is needed is a guesser for code page and normalization. Or - at least as likely - I am missing some important information.

The best possible option, however, would be a Smalltalk dialect that speaks UTF-8 natively. I create a String and don't care at all. I guess this is going to remain a dream for a while... ;-)


Joachim


 

- Seth
On Monday, December 7, 2020 at 3:59:55 AM UTC-5 [hidden email] wrote:
HI Seth,


I am glad you're taking this Unicode thing very serious. It is a minefield that seems neverending ;-) Whenever I think I have finally solved all issues, some new esoteric case comes around the corner. So it is good you take your time to make things work. You think you've got UTF-8 conversion working, but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first.... You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!

So even if I'd love to have THE solution in my hands rather sooner than later, I prefer a working solution over one that's only half baked today ;-)

I'm looking forward to your updates on this.

Joachim








Seth Berman schrieb am Sonntag, 6. Dezember 2020 um 15:47:03 UTC+1:
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/60c9d1c6-671f-4c02-8d50-5e09c5a8fc53n%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

Adriaan van Os-3
I like those images. Keep it coming. 😊

On Friday, December 18, 2020 at 4:25:12 PM UTC+1 Seth Berman wrote:
Not sure if the pictures showed from the last message, so I'll try attaching a screenshot
unicodeScreenshot.png

On Friday, December 18, 2020 at 10:21:58 AM UTC-5 Seth Berman wrote:
Hello Joachim,

Thanks for your thoughts on this, much appreciated.
If I were to sum up your points, I would put them at "handling normalization for me" and "Guess the character encoding of an input string"

"Handling normalization for me"
I'll be honest with you, if having this abstracted away was the measure of "proper Unicode support", then most languages today would fail.
This is one of the reasons that I have chosen to have the basic unit of a UnicodeString at the grapheme level as opposed to answering Unicode scalars by default.
The other major reason is to properly deal with the plethora of API's which a UnicodeString is going to have to support as a result of being way down the
Collection hierarchy.  Take something simple like 'reverse'.  Try doing that with an NFD form of 'ü' and see what you get...you won't like it.

Most languages...even the so-called ones with "Unicode support" (which by the way is a near meaningless phrase) make you deal with normalization as just part of what you do.
For example, making sure that things are in NFC (or NFD) before you do these kinds of things is always on you to deal with.

I have studied most language's implementations of Unicode at this point and believe Swift and Perl 6 (now Raku) made the appropriate decisions with how they are representing this.
Its certainly far more complexity hoisted onto the implementer to do this, but anything lower level just leads us back to the same conversations regarding normalization.

With what has been developed, 'überweisung' at: 1 in your example answers the grapheme 'ü'.  It may be in NFC or NFD...it doesn't really matter for operations like '=' and 'hash'
and '<'.  However, we do maintain the original normalization under the hood because, for example, certain filesystems may require the name to be in some
normalized form or it won't find it.

The bottom line in this issue is that you will be dealing with graphemes (@ user-perceived characters) by default.  You won't care about normalization until you have some sort of
scenario where ensuring a normalized form is important.  And when you do, its easy.  We have asNFC, asNFD, asNFKC, asNFKD.  And you have the option to do it in-place or make a copy
or even just to test if your unicode string is in some normalized form.

If you want to work with something else, we have the analog of swift "views".  For us, these are bi-directional streams that subclass Stream and work on UnicodeString, Grapheme, and UnicodeScalar
You can do the normal next, atEnd, do: inject:into: and I'll be adding string slicing equivalents.
Right now the views are graphemes, unicodeScalars, utf8, utf16, utf32.
These views keep internal bookmarks to the bytes of the underlying implementation (which have nothing to do with how it is being presented to the user) so you get efficient O(n) streaming.

1 byte = 1 char = 1 user-perceived character is absolutely false, and trying to maintain that illusion is what got everybody into that codepage mess in the pre-unicode era.
And, it continues to get people into messes even during the unicode era because 1 Unicode codepoint = 1 character is also absolutely false....as you have found out.

"Guess the character encoding of an input string
I think if you do some research on this you'll see that, in general, you can't do this in a way that is 100% guaranteed to work.
Anybody that attempts to do this has to provide a confidence level of sorts and you even may have to provide a sufficient amount of input for it to even work.
We implement our Unicode in rust, so if I find a rust 'crate' that exposes this capability, I'll certainly look at wrapping it and would be happy to do so.
You can separately look at wrapping libICUs capability for it: http://userguide.icu-project.org/conversion/detection

As said previously, I plan to do a post to show some examples of where we are at that should be interesting.

Here is a small example.  I tend to use emojis because they are complex under the hood.
Part of this effort is to also ensure that workspaces and inspectors have scintilla editors in UTF-8 mode,
which is why its displaying the emoji and not gibberish.
I used this as my first test case to here https://hsivonen.fi/string-length/
The first screenshot is the emoji as  UnicodeString and it matches up with the website.
The second screenshot is the first of five unicodeScalars that make up the emoji.


- Seth

On Friday, December 18, 2020 at 8:09:22 AM UTC-5 [hidden email] wrote:
Hi Seth,

sorry for my late reply. I'm having crazy days, but who doesn't ?


Seth Berman schrieb am Montag, 7. Dezember 2020 um 14:43:41 UTC+1:
Hi Joachim,

"... but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first"
 
- In our UnicodeString, you don't typically have to think about normalization...unless you wish too.  The apis for NFC, NFD, NFKC, NFKD are there if you want them.
A new UnicodeString is logically composed of extended grapheme clusters.  Other ways to express that are "user-perceived character" or "those things your cursor hops over as you press the left and right key".
=, <, hash and so on with our Grapheme will ensure a common normalized form under the hood before performing the operation.

Well, I guess you are talking about a class that will be available in VAST 2021?
When all you have is iconv() or better #convertFromCodePage:, you have to deal with normalization. I only learned this year that an ü is not necessarily an ü. We could handle file uploads of files with names including German umlauts just fine. Until our first Mac user came along and uploaded a file where the ü was encoded as a sequence of the two dots and an u. iconv() doesn't work with these. In the end the words 'überweisung" and 'überweisung' were not the same any more... it took some while to understand.
This is not a VAST bug or anything, just a consequence of not having proper unicode support in your IDE. Soon this will be over. Unfortunately, not with 2021.


 
"...You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!"
I just want to make sure I understand.  Is your expectation that a UnicodeString can be reified from data for which the encoding (i.e. UTF-8, UTF-16LE...) is not known?

Exactly. This is why I asked for some LibICU functions in VASt some time ago which help with guessing whether a String can be assumed as UTF-8 or UTF-16 or ISO-8859. 
This is quite important, because if you send #convertFromCodepage: 'UTF-8' to a String which has already been converted, you risk getting an error code 84 from iconv(). iconv does not check whether the conversion makes sense or not, it simply fails. Which is okay, you just have to deal with it.
So the best thing to do is to try and call convertFromCodePage: a few times inside an exception handler and see if one of the results looks similar to what you'd like to get. So I ended up with a method that tries to guess whether a String is in UTF-8 by converting it from utf-8 to the local codepage and back and if the end result looks like the first string, it seems to be utf-8. If I get an exception on the way, I guess it is already in the local codepage (ISO-8859-1 in my case). It would be good to add some BOM checks and such to this, so that you can already tell if the String is UTF-8 or -16 before calling iconv twice, but then there still are Strings that do not come with a BOM, so I didn't go that extra mile.

Again, I only mention this to explain why I am eagerly waiting for libicu or something equivalent, not to be offensive about VAST.
 
I suppose you could attempt to first create from utf8, then utf16le, then utf16be and wait for one that works...but an encoding-guesser wasn't something that was going to be part of this.

Well, I don't really care if guessing and conversion are combined in a single call. If I can just say: 'Make this ISO-8859-1, no matter what it is now', I am also happy. I'm not keen on doing this on my own all the time.
AFAIU, libicu cannot do that. So I'm afraid a guesser is something that will be needed pretty soon after the conversion is done.
 
Or when you say "encoding"...are you referring to normalization?

You know, I actually don't know. In my naive picture, if I want a String converted from UTF-8 to the local codepage, I don't really care how an ü is encoded in the UTF-8 String. I want my ü ;-) OTOH, if I send the String back to a web browser (e.g. in a web app) I have to send it in UTF-8, and it seems there is no way I can ignore that. If I receive an Ü in one normalization scheme from the browser and send it back in another one, I am asking for trouble as soon as there is, say, Javascript code running on the client/Browser that compares the String or such.

I know this is not something that can be solved easily. I'd have to keep track of what normalization scheme the ü was encoded with when I received it from the Browser and when I send it back use that info to re-apply that scheme before sending the String down.
With only iconv() I have no chance to even find out.

So I guess what is needed is a guesser for code page and normalization. Or - at least as likely - I am missing some important information.

The best possible option, however, would be a Smalltalk dialect that speaks UTF-8 natively. I create a String and don't care at all. I guess this is going to remain a dream for a while... ;-)


Joachim


 

- Seth
On Monday, December 7, 2020 at 3:59:55 AM UTC-5 [hidden email] wrote:
HI Seth,


I am glad you're taking this Unicode thing very serious. It is a minefield that seems neverending ;-) Whenever I think I have finally solved all issues, some new esoteric case comes around the corner. So it is good you take your time to make things work. You think you've got UTF-8 conversion working, but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first.... You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!

So even if I'd love to have THE solution in my hands rather sooner than later, I prefer a working solution over one that's only half baked today ;-)

I'm looking forward to your updates on this.

Joachim








Seth Berman schrieb am Sonntag, 6. Dezember 2020 um 15:47:03 UTC+1:
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/5e1dd7ea-1334-4bf2-aec6-726c70963a4bn%40googlegroups.com.
Reply | Threaded
Open this post in threaded view
|

Re: parsing rfc2184 - compliant utf-8 text

Seth Berman
👍 

oh wait, I guess now it would be...
thumbsUp.png 

Also, the views are still "under construction"...so it will probably answer sizes in terms of code units.
For now utf8, utf16, utf32 views are answering size in terms of bytes which is why you see those numbers in the previous screenshot.

- Seth

On Friday, December 18, 2020 at 12:38:46 PM UTC-5 [hidden email] wrote:
I like those images. Keep it coming. 😊

On Friday, December 18, 2020 at 4:25:12 PM UTC+1 Seth Berman wrote:
Not sure if the pictures showed from the last message, so I'll try attaching a screenshot
unicodeScreenshot.png

On Friday, December 18, 2020 at 10:21:58 AM UTC-5 Seth Berman wrote:
Hello Joachim,

Thanks for your thoughts on this, much appreciated.
If I were to sum up your points, I would put them at "handling normalization for me" and "Guess the character encoding of an input string"

"Handling normalization for me"
I'll be honest with you, if having this abstracted away was the measure of "proper Unicode support", then most languages today would fail.
This is one of the reasons that I have chosen to have the basic unit of a UnicodeString at the grapheme level as opposed to answering Unicode scalars by default.
The other major reason is to properly deal with the plethora of API's which a UnicodeString is going to have to support as a result of being way down the
Collection hierarchy.  Take something simple like 'reverse'.  Try doing that with an NFD form of 'ü' and see what you get...you won't like it.

Most languages...even the so-called ones with "Unicode support" (which by the way is a near meaningless phrase) make you deal with normalization as just part of what you do.
For example, making sure that things are in NFC (or NFD) before you do these kinds of things is always on you to deal with.

I have studied most language's implementations of Unicode at this point and believe Swift and Perl 6 (now Raku) made the appropriate decisions with how they are representing this.
Its certainly far more complexity hoisted onto the implementer to do this, but anything lower level just leads us back to the same conversations regarding normalization.

With what has been developed, 'überweisung' at: 1 in your example answers the grapheme 'ü'.  It may be in NFC or NFD...it doesn't really matter for operations like '=' and 'hash'
and '<'.  However, we do maintain the original normalization under the hood because, for example, certain filesystems may require the name to be in some
normalized form or it won't find it.

The bottom line in this issue is that you will be dealing with graphemes (@ user-perceived characters) by default.  You won't care about normalization until you have some sort of
scenario where ensuring a normalized form is important.  And when you do, its easy.  We have asNFC, asNFD, asNFKC, asNFKD.  And you have the option to do it in-place or make a copy
or even just to test if your unicode string is in some normalized form.

If you want to work with something else, we have the analog of swift "views".  For us, these are bi-directional streams that subclass Stream and work on UnicodeString, Grapheme, and UnicodeScalar
You can do the normal next, atEnd, do: inject:into: and I'll be adding string slicing equivalents.
Right now the views are graphemes, unicodeScalars, utf8, utf16, utf32.
These views keep internal bookmarks to the bytes of the underlying implementation (which have nothing to do with how it is being presented to the user) so you get efficient O(n) streaming.

1 byte = 1 char = 1 user-perceived character is absolutely false, and trying to maintain that illusion is what got everybody into that codepage mess in the pre-unicode era.
And, it continues to get people into messes even during the unicode era because 1 Unicode codepoint = 1 character is also absolutely false....as you have found out.

"Guess the character encoding of an input string
I think if you do some research on this you'll see that, in general, you can't do this in a way that is 100% guaranteed to work.
Anybody that attempts to do this has to provide a confidence level of sorts and you even may have to provide a sufficient amount of input for it to even work.
We implement our Unicode in rust, so if I find a rust 'crate' that exposes this capability, I'll certainly look at wrapping it and would be happy to do so.
You can separately look at wrapping libICUs capability for it: http://userguide.icu-project.org/conversion/detection

As said previously, I plan to do a post to show some examples of where we are at that should be interesting.

Here is a small example.  I tend to use emojis because they are complex under the hood.
Part of this effort is to also ensure that workspaces and inspectors have scintilla editors in UTF-8 mode,
which is why its displaying the emoji and not gibberish.
I used this as my first test case to here https://hsivonen.fi/string-length/
The first screenshot is the emoji as  UnicodeString and it matches up with the website.
The second screenshot is the first of five unicodeScalars that make up the emoji.


- Seth

On Friday, December 18, 2020 at 8:09:22 AM UTC-5 [hidden email] wrote:
Hi Seth,

sorry for my late reply. I'm having crazy days, but who doesn't ?


Seth Berman schrieb am Montag, 7. Dezember 2020 um 14:43:41 UTC+1:
Hi Joachim,

"... but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first"
 
- In our UnicodeString, you don't typically have to think about normalization...unless you wish too.  The apis for NFC, NFD, NFKC, NFKD are there if you want them.
A new UnicodeString is logically composed of extended grapheme clusters.  Other ways to express that are "user-perceived character" or "those things your cursor hops over as you press the left and right key".
=, <, hash and so on with our Grapheme will ensure a common normalized form under the hood before performing the operation.

Well, I guess you are talking about a class that will be available in VAST 2021?
When all you have is iconv() or better #convertFromCodePage:, you have to deal with normalization. I only learned this year that an ü is not necessarily an ü. We could handle file uploads of files with names including German umlauts just fine. Until our first Mac user came along and uploaded a file where the ü was encoded as a sequence of the two dots and an u. iconv() doesn't work with these. In the end the words 'überweisung" and 'überweisung' were not the same any more... it took some while to understand.
This is not a VAST bug or anything, just a consequence of not having proper unicode support in your IDE. Soon this will be over. Unfortunately, not with 2021.


 
"...You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!"
I just want to make sure I understand.  Is your expectation that a UnicodeString can be reified from data for which the encoding (i.e. UTF-8, UTF-16LE...) is not known?

Exactly. This is why I asked for some LibICU functions in VASt some time ago which help with guessing whether a String can be assumed as UTF-8 or UTF-16 or ISO-8859. 
This is quite important, because if you send #convertFromCodepage: 'UTF-8' to a String which has already been converted, you risk getting an error code 84 from iconv(). iconv does not check whether the conversion makes sense or not, it simply fails. Which is okay, you just have to deal with it.
So the best thing to do is to try and call convertFromCodePage: a few times inside an exception handler and see if one of the results looks similar to what you'd like to get. So I ended up with a method that tries to guess whether a String is in UTF-8 by converting it from utf-8 to the local codepage and back and if the end result looks like the first string, it seems to be utf-8. If I get an exception on the way, I guess it is already in the local codepage (ISO-8859-1 in my case). It would be good to add some BOM checks and such to this, so that you can already tell if the String is UTF-8 or -16 before calling iconv twice, but then there still are Strings that do not come with a BOM, so I didn't go that extra mile.

Again, I only mention this to explain why I am eagerly waiting for libicu or something equivalent, not to be offensive about VAST.
 
I suppose you could attempt to first create from utf8, then utf16le, then utf16be and wait for one that works...but an encoding-guesser wasn't something that was going to be part of this.

Well, I don't really care if guessing and conversion are combined in a single call. If I can just say: 'Make this ISO-8859-1, no matter what it is now', I am also happy. I'm not keen on doing this on my own all the time.
AFAIU, libicu cannot do that. So I'm afraid a guesser is something that will be needed pretty soon after the conversion is done.
 
Or when you say "encoding"...are you referring to normalization?

You know, I actually don't know. In my naive picture, if I want a String converted from UTF-8 to the local codepage, I don't really care how an ü is encoded in the UTF-8 String. I want my ü ;-) OTOH, if I send the String back to a web browser (e.g. in a web app) I have to send it in UTF-8, and it seems there is no way I can ignore that. If I receive an Ü in one normalization scheme from the browser and send it back in another one, I am asking for trouble as soon as there is, say, Javascript code running on the client/Browser that compares the String or such.

I know this is not something that can be solved easily. I'd have to keep track of what normalization scheme the ü was encoded with when I received it from the Browser and when I send it back use that info to re-apply that scheme before sending the String down.
With only iconv() I have no chance to even find out.

So I guess what is needed is a guesser for code page and normalization. Or - at least as likely - I am missing some important information.

The best possible option, however, would be a Smalltalk dialect that speaks UTF-8 natively. I create a String and don't care at all. I guess this is going to remain a dream for a while... ;-)


Joachim


 

- Seth
On Monday, December 7, 2020 at 3:59:55 AM UTC-5 [hidden email] wrote:
HI Seth,


I am glad you're taking this Unicode thing very serious. It is a minefield that seems neverending ;-) Whenever I think I have finally solved all issues, some new esoteric case comes around the corner. So it is good you take your time to make things work. You think you've got UTF-8 conversion working, but then some file gets uploaded with Umlauts being encoded in some form that convertFromCodePage: (or better iconv() ) doesn't handle, because it requires normalization first.... You get a file with unknown encoding and you need to make sure it is displayed correctly, but it is almost impossible to tell what encoding it might be in, especially if there is no BOM ... and whatnot!

So even if I'd love to have THE solution in my hands rather sooner than later, I prefer a working solution over one that's only half baked today ;-)

I'm looking forward to your updates on this.

Joachim








Seth Berman schrieb am Sonntag, 6. Dezember 2020 um 15:47:03 UTC+1:
Hello Joachim,

I saw you had some questions about Unicode.  In the next week or two I will share some more details regarding it.
VAST 2021 is a massive release (again) and I just decided yesterday that Unicode won't quite be ready without a significant delay to VAST 2021.
There are simply too many subtle design decisions that can end up breaking the product if they go badly (and end up costing the company a lot of money to fix which I'm not going to let happen;)
On a positive note, the Unicode support library was very nearly going to make it...and we will follow up with an ECAP or specific-customer program shortly after the release so folks can start working with it and help us make it as useful as possible in a follow on release.  This should be in Feb.

No, its not ICU.  Our Unicode support in the VM is written almost exclusively in the rust programming language and it has given us an interesting edge and tools for a fast/modern string implementation.
Our UnicodeString, Grapheme and UnicodeScalar abstractions and APIs are highly inspired by the Swift programming language.

As I said, I'll follow up in the next week or two with a post that shows how things work.

- Seth


The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

On Thursday, November 5, 2020 at 7:13:35 AM UTC-5 [hidden email] wrote:
Hans-Martin,

thanks fpr your comments. I am currently decoding the %-encoded charecters first and then I use convertFromCodePage: 'utf-8' on the resultung String. So far this seems to work well for German texts, even fpr umlauts that are not combined diarhesis ones. But your comment sparks an idea that I could try for at least 16rCC's which follow an a,e,o or u. and are follewd by an 16r88. I could implement such a special case handling for at least German umlauts (which form the majority in our user base) and ignore all others. That's probably the lowest hanging fruit I can get and still handle maybe 95% of relevant cases. Nothing to be proud of, more a tsttcpw without annoying most of our users too much.

I already baught myself a bunch of T-shits with 'I ? Unicode' printed on them a while ago, because this unicode stuff is a minefield. One of the few fields that make me envious of other prgramming languages that natively use utf-8.

Joachim




[hidden email] schrieb am Mittwoch, 4. November 2020 um 11:36:19 UTC+1:
First of all, you need to parse %-encoded hex data in UTF-8 differently. Consecutive %-encoded bytes may form a single code point, in this case %cc%88 -> \u00A8, which is the combining diacritical mark. Perhaps you already do this (when you convert from code page 'utf-8').
This then has to be combined with the previous base character 'u' (\u0075) to yield the final 'ü' character (\u00FC).
Btw, what I get when converting this sequence from UTF8 looks like this:
  #[16r75 16rCC 16r88] asString convertFromCodePage: 'utf-8' 'u¨'
so except for combining the diacritical mark with the base it's almost right.

Waiting for Unicode Support is probably the most reasonable choice, as these things tend to get really icky if you try to code them yourself, especially if you're not fully fluent with all the conventions and rules of Unicode.

Cheers,
Hans-Martin


[hidden email] schrieb am Donnerstag, 29. Oktober 2020 um 12:32:16 UTC+1:
I am a step further... I was right about the normalizeation stuff. According to https://www.utf8-chartable.de/unicode-utf8-table.pl

0xcc and 0x88 are the  NFD form of combining the previous character with a Diaeresis. Seems like all sequences starting with at least CC and CD combine a character with some "extra" like cedilla, circonflex and whatnot. So I would need to use some normalization function convert this to NFC first and the convertFromCodePage: 'utf-8'.

So what would be needed is either a nice algorithm to normalize at least my subset of characters or a binding to some library like ICU.

The VAST roadmap says there will be a Unicode Support Library in VAST 2021 (scheduled for Q4/2020). I guess this refers to LibICU. So there is probably not much point in investing much time in my own incomplete and buggy implementation if Instantiations ships this next release in a few weeks that'll include exactly what I need in a tested and solid library...?

Is there anything that can be said about this publicly at the moment or do I have to wait for VAST 2021 and be surprised ;-) ?

For now, I'll probably just skip all %cc and the following character and thus vonvert umlauts to their base characters (ü -> u) for such encodings...

Joachim


 

Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 11:31:05 UTC+1:
So in order to get a step further, I tried using the more or less equivalent (but also a little more deprecated) Content-Type-Header's name parameter in this mail message, which uses an RFC2047 encoding (A kind of variant of QuotedPrintable), in which my beloved ü looks like this: 'U=cc=88'.

The only difference is that here the Character signalling a non-ASCII character is an equal sign instead of percent.

So I have the very same problem here: this ü is a sequence of 0x75 0xcc 0x88, and I don't get anywhere with this other header as long as I don't know how to get this sequence converted to an ü in ISO-8859-1.

I guess this will all be solvable quite easily when VAST integrates ICU, at least when you know what functions to call, but what can I do until then?



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:20:38 UTC+1:
I just saw that Google Groups showed me an embedded image form my inspector when I edited the message, but it is now missing.

So here is another attempt to show you what my naive algorhithm turns  0x75 0xcc 0x88 into: 
'75' -> $u
'CC' -> $Ì
'88' -> $ˆ

So the question remains: what is the correct way to convert these three characters into an u umlaut in iso-8859-1 ? convertFromCodePage: 'utf-8' is not working on this combination...



Joachim Tuchel schrieb am Donnerstag, 29. Oktober 2020 um 10:15:16 UTC+1:

I am trying to read an attachment file name from a content-disposition header in a mail message received an fetched using IMAP.

The String in question looks easy. It contains every character in the form of %41 (=$A), so each character is encoded as a percent sign and a hex value.

Decoding seems easy:
char := inStream next.
(char = $%)
ifFalse: [outStream nextPut: char]
ifTrue: [|hex|
  hex := inStream next: 2.
  outStream nextPut: (Character codepoint:   (Integer readFrom: hex base: 16))].

So far so good. Until special characters come to play....

One of the messages I received has the letter ü encoded as %75%CC%88. Expressed in hex this would be 0x75 0xcc 0x88. Everybody knows this is a German small u umlaut.

Almost everybody, at least.

The algo above converts this to:

And if I try to convertFromCodePage: 'utf-8', this perfectly fails by returning 'u?'.
(BTW: I know for sure it is utf-8, because that is what the header explicitly states)


So, it is time to freely admit I am still not getting all this web and utf-8 stuff.

I know I came along issues like several forms of 'ü' encoded if utf-8 and these could be solved in javascript on the Browser side by normalizing. But I don't think VAST supports any of these normalization functions (NFD, NFC, whatever).

So does anybody know what I can do to get these Strings converted to my local Codepage (iso-8859-1 or -15)? I don't need a complete solution for now, but accented characters form German, French and maybe Czech, Polish etc. should be possible.

Any hints?

Joachim





--
You received this message because you are subscribed to the Google Groups "VA Smalltalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To view this discussion on the web visit https://groups.google.com/d/msgid/va-smalltalk/6acaa3f6-20df-4be8-8c0b-e9f48600d8f0n%40googlegroups.com.