[Gelöst] OutStream.WRITE ergänzt hexadezimal 00

17. Mai 2016 14:40

Irgendwann <= NAV 2016 hat sich geändert, wie NAV per OutStream in Dateien schreibt.

Zum Vergleich (getestet mit ANSI und UTF-8), nach "mein Text" folgt ...

File.WRITE('mein Text')ein CRLF (13 + 10)
OutStream.WRITETEXT('mein Text")nichts - übrigens unabhängig davon, ob die Datei mit oder Textmode geöffnet ist.
OutStream.WRITE("mein Text")ein NULL (hexadezimal 00) an. Wieder unabhängig vom Textmode.


OutStream.WRITE muss sich zwischendurch (spätestens aber nach NAV 2009) geändert haben. Die Onlinehilfe schweigt zu dieser Änderung (der Ergänzung von 00).
Dieser Tatsache ist zu verdanken, dass eine Datei, die als UTF-8 erstellt worden ist und per OutStream.WRITE Buchstabe für Buchstabe (!) befüllt wird, dennoch - trotz enthaltener Umlaute - im ANSI-Format endet.
Ist das jetzt ein Bug oder ein Feature?

Wer es selbst testen möchte, hier die Erzeugung von sehr simplen UTF8-Dateien (ohne meine Buchstabe-für-Buchstabe-Befüllung):

Code:
NewEncoding := TEXTENCODING::UTF8;
FolderName := 'UTF8';

// File.WRITE schließt immer mit einem CRLF ab

TestFile.TEXTMODE := TRUE;
TestFile.CREATE('C:\Temp\' + FolderName + '\OhneTextmode_2Umlaute.txt',NewEncoding);
TestFile.WRITE('äöüß'); // + CRLF
TestFile.CLOSE;

// OutStream.WRITE fügt nach jedem WRITE an NULL (00) an. Textmode egal!

TestFile.TEXTMODE := TRUE;
TestFile.CREATE('C:\Temp\' + FolderName + '\OhneTextmode_2Umlaute_Streaming_TextmodeTRUE_WRITE.txt',NewEncoding);
TestFile.CREATEOUTSTREAM(NAVOutStream);
NAVOutStream.WRITE('äöüß'); // + NULL
TestFile.CLOSE;

TestFile.TEXTMODE := FALSE;
TestFile.CREATE('C:\Temp\' + FolderName + '\OhneTextmode_2Umlaute_Streaming_TextmodeFALSE_WRITE.txt',NewEncoding);
TestFile.CREATEOUTSTREAM(NAVOutStream);
NAVOutStream.WRITE('äöüß'); // + NULL
TestFile.CLOSE;


// OutStream.WRITETEXT fügt nichts mehr an - so wie es sein soll. Textmode egal!
TestFile.TEXTMODE := TRUE;
TestFile.CREATE('C:\Temp\' + FolderName + '\OhneTextmode_2Umlaute_Streaming_TextmodeTRUE_WRITETEXT.txt',NewEncoding);
TestFile.CREATEOUTSTREAM(NAVOutStream);
NAVOutStream.WRITETEXT('äöüß'); // + gar nichts
TestFile.CLOSE;

TestFile.TEXTMODE := FALSE;
TestFile.CREATE('C:\Temp\' + FolderName + '\OhneTextmode_2Umlaute_Streaming_TextmodeFALSE_WRITETEXT.txt',NewEncoding);
TestFile.CREATEOUTSTREAM(NAVOutStream);
NAVOutStream.WRITETEXT('äöüß'); // + gar nichts
TestFile.CLOSE;

Re: OutStream.WRITE ergänzt hexadezimal 00

19. Mai 2016 13:19

Natalie hat geschrieben:Ist das jetzt ein Bug oder ein Feature?
Hat echt niemand eine Meinung dazu?

Re: OutStream.WRITE ergänzt hexadezimal 00

23. Mai 2016 12:47

Zumindest ist es unsauber, weil es in einer objektorientierten Sprache eigentlich nicht mehr dazu gehört.

Aus Unicode 4.0
For example, a string is defined as a pointer to char in the C language, and is conventionally terminated with a NULL character. In object-ori-
ented languages, a string is a complex object, with associated methods, and its value may or may not consist of merely a code unit sequence.


Leider ist UTF-8 bei den Null-Zeichen auch variabel.
Es gibt in UTF-8 sowohl Single Byte als auch Double Byte "Nulls" in Modified UTF-8
In Modified UTF-8 the null character (U+0000) is encoded as 0xC0,0x80. Modified UTF-8 strings never contain any actual null bytes but can contain all Unicode code
points including U+0000


Die Double-Byte-Variante ist aber Sicherheitsrisiko
Aus Null-terminated string
Some systems use "modified UTF-8" which encodes the NUL character as two non-zero bytes (0xC0, 0x80) and thus allow all possible strings to be stored. (this is not allowed by the UTF-8 standard as it is a security risk. A C0,80 NUL might be seen as a string terminator in security validation and as a character when used)

Re: OutStream.WRITE ergänzt hexadezimal 00

23. Mai 2016 13:20

OK danke, ich nehme das mal zum Anlass, es doch Microsoft zu melden - Supportfall 116052314197082. Ich halte euch auf dem Laufenden.

Re: OutStream.WRITE ergänzt hexadezimal 00

23. Mai 2016 16:49

Natalie hat geschrieben:OK danke, ich nehme das mal zum Anlass, es doch Microsoft zu melden - Supportfall 116052314197082. Ich halte euch auf dem Laufenden.


Meine Vermutung ist das gesagt wird:

"Nimm doch NAVOutStream.WRITETEXT" und die Anderen, die einen null-terminierten String möchten, nehmen NAVOutStream.WRITE - Problem gelöst. 8-)

mfg,
winfy

Re: OutStream.WRITE ergänzt hexadezimal 00

23. Mai 2016 16:56

winfy hat geschrieben:"Nimm doch NAVOutStream.WRITETEXT" und die Anderen, die einen null-terminierten String möchten, nehmen NAVOutStream.WRITE - Problem gelöst. 8-)
Der Vorschlag mit WRITETEXT kam auch schon ;-)
Aber meiner bisherigen Erfahrung nach tut sich MS mit Crash-Meldungen nicht einfach ab, sondern versucht schon, die Crash-Ursache zu beheben.

Re: OutStream.WRITE ergänzt hexadezimal 00

26. Mai 2016 16:29

Ich weiß nicht, ob das so neu ist. Hier gab es das Gleiche schon mal vor vielen Jahren. Vielleicht hat sich NAV auch zwischenzeitlich mehrfach geändert? Wäre ja nicht das erste mal.

Re: OutStream.WRITE ergänzt hexadezimal 00

29. Juni 2016 09:16

Besser spät als nie, hier nun die "Auflösung".

Grundsätzlich ist die Ergänzung des 00 nichts Neues, sondern schon seit jeher beabsichtigt, wenn auch nicht ausreichend dokumentiertes Verhalten, wie auch z.B. folgende, uralte Links zeigen:
InStream and OutStream
However, bare in mind that there are 2 ways of writting and 2 ways of reading Streams: binary and text wised. So, whenever you are using OutStream.Write, you are writting binary, and when using OutStream.WriteTEXT, you are, literally writting only the text part of the parameter.

So, when using READ, versus READTEXT, you will be seeking for the NULL termination that the binary text will WRITE out. That is why you will get an AL error if this NULL termination was not found.


null-terminated stream
[...] strings in NAV are containing their length in the first character. So, they are not null-terminated. [...] With an outstream variable you will get the null-terminated string / zero byte as EOS if you use OutS.WRITE.

Bemerkung: Mittlerweile (mind. seit NAV 2009) sind auch in C/AL die Strings null-terminiert, siehe ganz unten in diesem Beitrag.

Der Unterschied zu früher besteht nur darin, ob man die Datei Text- oder Char-weise schreibt. Nur bei den Chars hat sich etwas geändert:
MS Developers hat geschrieben:Chars in AL are by default two bytes [seit NAV 2013]. The NULLs written between each of the characters of the string are just the lower/higher byte of the character retrieved from the string.
As a work-around, assign a byte variable to the value of the character extracted from the string and write that. (Beispiel: siehe Byte Data Type) This will cause the byte sequence to be written to the stream without any injected zeros (NULLs).

Oder ein bisschen einfacher erklärt:
MS Support hat geschrieben:I think NAV 2009 chars had 1 byte and only current NAV becomes Unicode (2 bytes). So NAV 2009 just didn’t need NULL.


Nullzeichen
Nun noch ein paar Worte zum "null terminator" (dt. Nullzeichen). Wenn man die Begriffe erst einmal kennt, dann wird man auch in der Onlinehilfe fündig,
Artikel Char Data Type:
  • NAV 2009
    8 Bit, Werte von 0..255
    Classic Client:
    In the Classic client, you can assign a char to any position within the defined length of a text or code variable.
    Als Beispiel wird genannt, dass wenn MyText = 'abc' (die max. Länge aber scheinbar größer) ist, dann ist das der Nullzeichen an Position 4. Schreibe ich einen Char an Position 5, dann ist das möglich, aber das Nullzeichen bleibt an Position 4 und der Text wird weiterhin als 'abc' angezeigt. Schreibe ich danach einen Character an Position 4, wandert das Nullzeichen an Position 6. Der Text wird nun mit 5 Stellen ausgegeben.
    Role Tailored Client:
    Beim Versuch, an eine Stelle größer als die des Nullzeichens zu schreiben, tritt ein Laufzeitfehler auf.

  • NAV 2013 (und alle neueren)
    16 Bit, Werte 0..65535
    Unicode character that is represented internally as a 16-bit unsigned integer. (Quelle)
    Verwendung/Einschränkungen genau wie Chars im RTC von NAV 2009.
    Der Byte Datentyp sieht noch immer genauso aus und verhält sich wie Char unter NAV 2009 RTC: 8 Bit.

PowerShell: Entfernen des NULL-Zeichens aus Datei

24. Januar 2017 10:31

Nachdem ich in einem Projekt diese NULLs aus Textdateien vor der Übertragung loswerden musste, hier zwei Lösungen mit PowerShell (für Default-Codepage 850, sonst ggf. anpassen).
Die Funktionen werden zusammen mit dem Pfad zur Textdatei aufgerufen.

Etwas aufwendiger, aber bessere Lösung, funktioniert auch bei Dateien im Unix/Linux-Textformat (mit LF statt CR LF für den Zeilenumbruch) und fügt der neuen Datei keine Leerzeile hinzu.
Code:
Function RemoveNULLs
{
## No blank line at the end, keeps LF for Unix/Linux Systems
$sourceEncoding = [System.Text.Encoding]::GetEncoding(850)
$targetEncoding = [System.Text.Encoding]::GetEncoding(850)
$args = resolve-path $args
$filecontent = [System.IO.File]::ReadAllText($args,$sourceencoding).Replace("`0","")
[System.IO.File]::WriteAllText($args, $filecontent,$targetEncoding)
}

Info dazu: Avoid Blank Lines at End of a Text File with PowerShell

Methode für manuelles "Quick and Dirty" (nicht wenn Datei im UNIX-Format ist (konvertiert LF zu CR LF) und hinterher zusätzliche Leerzeile am Ende)
Code:
Function RemoveNULLs2
{
### Adds blank line at the end and converts LF to CR LF
(Get-Content $args -encoding Oem) -replace "`0", ""  | Set-Content $args -encoding Oem
}


Aufruf aus NAV heraus ist möglich wie hier beschrieben.