AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

TEdit + Steuerzeichen + XML = Exception

Ein Thema von Whookie · begonnen am 2. Jul 2025 · letzter Beitrag vom 4. Jul 2025
Antwort Antwort
Whookie

Registriert seit: 3. Mai 2006
Ort: Graz
453 Beiträge
 
Delphi 10.3 Rio
 
#1

TEdit + Steuerzeichen + XML = Exception

  Alt 2. Jul 2025, 15:14
Heute bin ich auf ein (im nach hinein nachvollziehbares) aber doch gut verstecktes Problem gestoßen:
Ich hab ein umfangreiches Programm in dem der Anwender verschiedene "Objekte" anlegen und benennen kann. Das ganze wird dann als XML-Datei gespeichert (z.B.: <Name>Mein Name</Name>).

Das funktioniert schon jahrelang so aber gestern war dann ein Teil der Datei nicht mehr vorhanden und nach eingehender Analyse liegt der Grund in der Überschrift verpackt! Der Auslöser war der Versuch einen Namen wie "Mein_Test" einzugeben. Dabei ist der Anwender beim Underline unabsichtlich statt auf Umsch+'-' an ein Strg+Umsch+'-' geraten. Das bewirkt aber nicht wirklich etwas (ein Schelm wer da jetzt böses denkt), daher hat er dann nochmal ein "_" eingegeben und den Namen vollendet.

Das funktioniert solange problemlos, bis man versucht die Konfiguration abzuspeichern, den XML mag keine Steuerzeichen und Strg+Umsch+'-' ergibt #$001F (Unit Seperator). Weil der kleine Kerl unsichtbar bleibt ist das natürlich doppelt fies aber gut er ist nicht allein, die meisten Steuercodes im Bereich von $1 - $1f (mit den bekannten Ausnahmen) sind nicht so ganz XML-kompatibel.

Daher jetzt die Frage wie macht ihr das, wenn ihr XML als Dateiformat verwendet. Filtert ihr da einfach alle Eingaben direkt beim Edit-Feld?

Ich würde mir ja eine property wünschen, damit das Editfeld so was erst gar nicht annimmt?
Whookie

Software isn't released ... it is allowed to escape!
  Mit Zitat antworten Zitat
Stolle58

Registriert seit: 4. Aug 2023
Ort: Frankfurt
37 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: TEdit + Steuerzeichen + XML = Exception

  Alt 2. Jul 2025, 16:20
Hallo,

ich würde das im OnKeyPress-Event filtern.

if Ord(Key) <= 31 then
Reinhold
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.476 Beiträge
 
Delphi 12 Athens
 
#3

AW: TEdit + Steuerzeichen + XML = Exception

  Alt 2. Jul 2025, 18:11
Hier klöppelt aber niemand manuell Strings zusammen, die sowas wie ein XML ergeben sollen,
oder wird hier doch eine XML-Komponente genutzt, und wenn ja, warum behandelt oder kodiert die sowas nicht?

ich würde das im OnKeyPress-Event filtern.
Zum Glück kommt niemand auf so eine ungewöhnliche Idee Text einfach reinzukopieren.
Ein Therapeut entspricht 1024 Gigapeut.

Geändert von himitsu ( 2. Jul 2025 um 18:13 Uhr)
  Mit Zitat antworten Zitat
VoSs2o0o

Registriert seit: 23. Jan 2024
11 Beiträge
 
#4

AW: TEdit + Steuerzeichen + XML = Exception

  Alt Gestern, 07:58
Ich hatte das gleiche Problem gestern in Java, und habe es via Regex gelöst:

Code:
line = line.replaceAll("[\\x{00}-\\x{1f}&&[^\\x{09}\\x{0A}\\x{0D}]]+", " ");
Bedeutet soviel wie: "greife alle Zeichen von x00-x1f außer x09,x0A,x0D und ersetze diese durch Leerzeichen.
Ich denke [<group>&&[^<group>]] muss man in Delphis Regex-Engine aber anders schreiben...
Delphi Themen auf meiner Homepage: https://www.cloud-9.de/component/tags/tag/43-delphi
  Mit Zitat antworten Zitat
Whookie

Registriert seit: 3. Mai 2006
Ort: Graz
453 Beiträge
 
Delphi 10.3 Rio
 
#5

AW: TEdit + Steuerzeichen + XML = Exception

  Alt Gestern, 10:43
Hier klöppelt aber niemand manuell Strings zusammen, die sowas wie ein XML ergeben sollen,
oder wird hier doch eine XML-Komponente genutzt, und wenn ja, warum behandelt oder kodiert die sowas nicht?
Naja, "behandelt" wird das ja vom MSXML-Framework schon (mehr oder weniger). Das nennt sich Validation und führt zu ein Exception wenn so ein String einer Node zugewiesen wird.
Soweit ich das bisher herausgefunden habe gibt es dafür keine mitgelieferte Funktion, also führt dann wohl nichts um eine eigne Sanitize-Funktion herum.

Laut XML Spezifikation sollte das dann in RegEx etwa so aussehen [^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD] (was in Delphi natürlich nicht umsetzbar ist).

Daher habe ich mich für eine TStringBuilder - Lösung entschieden:

Delphi-Quellcode:
function SanitizeXML(const AValue: String): String;
var
  i: Integer;
  LChr: Char;
  LStrBldr: TStringBuilder;
begin
  LStrBldr := TStringBuilder.Create;
  try
    for i := 1 to Length(AValue) do
    begin
      LChr := AValue[i];
      if (LChr = #9) or (LChr = #10) or (LChr = #13) or
         ((LChr >= #32) and (LChr <= #$D7FF)) or
         ((LChr >= #$E000) and (LChr <= #$FFFD)) then
        LStrBldr.Append(LChr);
    end;
    Result := LStrBldr.ToString;
  finally
    LStrBldr.Free;
  end;
end;

Da ich damals das XML-Handling in einer eigenen XML-File Klasse verpackt habe, um einfach verschiedene XML-Libs zu testen, gibt es ohnehin nur zwei Stellen an denen eine Node.Value oder .Attribute zugewiesen wird und damit bin ich dann hoffentlich auf der sicheren Seite.

Gibt es dagegen bedenken oder etwas das noch schneller ist?
Whookie

Software isn't released ... it is allowed to escape!
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.079 Beiträge
 
Delphi 10.4 Sydney
 
#6

AW: TEdit + Steuerzeichen + XML = Exception

  Alt Gestern, 11:24
Gibt es dagegen bedenken oder etwas das noch schneller ist?
Vermute durch das Setzen der Capacity im Constructor kann man noch viel rausholen, der Rest ist eher just for fun:

Delphi-Quellcode:
function SanitizeXML(const AValue: String): String;
var
  i, Len: Integer;
  LChr: Char;
  LStrBldr: TStringBuilder;
begin
  Len := Length(AValue);
  LStrBldr := TStringBuilder.Create(Len);
  try
    for i := 1 to Len do
    begin
      LChr := AValue[i];
      case LChr of
        #9, #10, #13, #32..#$D7FF, #$E000..#$FFFD:
        LStrBldr.Append(LChr);
      end;
    end;
    Result := LStrBldr.ToString;
  finally
    LStrBldr.Free;
  end;
end;

Geändert von TiGü (Gestern um 11:26 Uhr)
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.079 Beiträge
 
Delphi 10.4 Sydney
 
#7

AW: TEdit + Steuerzeichen + XML = Exception

  Alt Gestern, 12:50
Na gut, es würde alles noch ein bisschen schneller gehen, wenn man bis zum Maximum optimiert:

Code:
Debug Output: Original TStringBuilder: 53,99 ms Process Project1.exe (27340)
Debug Output: Optimiert mit PChar: 48,4411 ms Process Project1.exe (27340)
Debug Output: Direkter Zugriff: 27,047 ms Process Project1.exe (27340)
Debug Output: Direkter Zugriff 2: 16,019 ms Process Project1.exe (27340)
Debug Output: Direkter Zugriff 3: 12,4765 ms Process Project1.exe (27340)
Delphi-Quellcode:
function SanitizeXML_Original(const AValue: String): String;
var
  i: Integer;
  LChr: Char;
  LStrBldr: TStringBuilder;
begin
  LStrBldr := TStringBuilder.Create;
  try
    for i := 1 to Length(AValue) do
    begin
      LChr := AValue[i];
      if (LChr = #9) or (LChr = #10) or (LChr = #13) or
         ((LChr >= #32) and (LChr <= #$D7FF)) or
         ((LChr >= #$E000) and (LChr <= #$FFFD)) then
        LStrBldr.Append(LChr);
    end;
    Result := LStrBldr.ToString;
  finally
    LStrBldr.Free;
  end;
end;

function SanitizeXML_PChar(const AValue: String): String;
var
  Src, SrcEnd: PChar;
  LStrBldr: TStringBuilder;
  C: Char;
begin
  LStrBldr := TStringBuilder.Create(Length(AValue));
  try
    Src := PChar(AValue);
    SrcEnd := Src + Length(AValue);
    while Src < SrcEnd do
    begin
      C := Src^;
      if (C = #9) or (C = #10) or (C = #13) or
         ((C >= #32) and (C <= #$D7FF)) or
         ((C >= #$E000) and (C <= #$FFFD)) then
        LStrBldr.Append(C);
      Inc(Src);
    end;
    Result := LStrBldr.ToString;
  finally
    LStrBldr.Free;
  end;
end;

function SanitizeXML_Direct(const AValue: String): String;
var
  Src: PChar;
  Dest: PChar;
  Len, i: Integer;
  C: Char;
begin
  Len := Length(AValue);
  SetLength(Result, Len);
  Src := PChar(AValue);
  Dest := PChar(Result);

  for i := 0 to Len - 1 do
  begin
    C := Src[i];
    if (C = #9) or (C = #10) or (C = #13) or
       ((C >= #32) and (C <= #$D7FF)) or
       ((C >= #$E000) and (C <= #$FFFD)) then
    begin
      Dest^ := C;
      Inc(Dest);
    end;
  end;

  SetLength(Result, Dest - PChar(Result));
end;

function SanitizeXML_Direct_2(const AValue: String): String;
var
  Src: PChar;
  Dest: PChar;
  Len, i: Integer;
  C: Char;
begin
  Len := Length(AValue);
  SetLength(Result, Len);
  Src := PChar(AValue);
  Dest := PChar(Result);

  for i := 0 to Len - 1 do
  begin
    C := Src[i];
    case C of
      #9, #10, #13, #32..#$D7FF, #$E000..#$FFFD:
      begin
        Dest^ := C;
        Inc(Dest);
      end;
    end;
  end;

  SetLength(Result, Dest - PChar(Result));
end;

function SanitizeXML_Direct_3(const AValue: String): String;
var
  Src, SrcEnd, Dest: PChar;
  Len: Integer;
begin
  Len := Length(AValue);
  SetLength(Result, Len);
  Src := PChar(AValue);
  Dest := PChar(Result);
  SrcEnd := Src + Len;

  while Src < SrcEnd do
  begin
    case Src^ of
      #9, #10, #13,
      #32..#$D7FF,
      #$E000..#$FFFD:
      begin
        Dest^ := Src^;
        Inc(Dest);
      end;
    end;
    Inc(Src);
  end;

  SetLength(Result, Dest - PChar(Result));
end;

procedure RunBenchmark;
const
  Iterations = 1000;
var
  i: Integer;
  InputStr, ResultStr1, ResultStr2, ResultStr3, ResultStr4, ResultStr5: String;
  SW: TStopwatch;
begin
  // Beispiel-XML mit gültigen/ungültigen Zeichen
  InputStr := StringOfChar('A', 10000) + #0 + #1 + #2 + #3 + #4 + #5 + #6 + #7 +#8 + #$FFFF + StringOfChar('B', 10000);

  // Original
  SW := TStopwatch.StartNew;
  for i := 1 to Iterations do
    ResultStr1 := SanitizeXML_Original(InputStr);
  SW.Stop;
  OutputDebugString(PChar('Original TStringBuilder: ' + SW.Elapsed.TotalMilliseconds.ToString + ' ms'));

  // PChar + TStringBuilder
  SW := TStopwatch.StartNew;
  for i := 1 to Iterations do
    ResultStr2 := SanitizeXML_PChar(InputStr);
  SW.Stop;
  OutputDebugString(PChar('Optimiert mit PChar: ' + SW.Elapsed.TotalMilliseconds.ToString + ' ms'));

  // Direkte Speicherzugriffe
  SW := TStopwatch.StartNew;
  for i := 1 to Iterations do
    ResultStr3 := SanitizeXML_Direct(InputStr);
  SW.Stop;
  OutputDebugString(PChar('Direkter Zugriff: ' + SW.Elapsed.TotalMilliseconds.ToString + ' ms'));

  // Direkte Speicherzugriffe mit case-of
  SW := TStopwatch.StartNew;
  for i := 1 to Iterations do
    ResultStr4 := SanitizeXML_Direct_2(InputStr);
  SW.Stop;
  OutputDebugString(PChar('Direkter Zugriff 2: ' + SW.Elapsed.TotalMilliseconds.ToString + ' ms'));

  // Direkte Speicherzugriffe mit while statt for und mit case-of
  SW := TStopwatch.StartNew;
  for i := 1 to Iterations do
    ResultStr5 := SanitizeXML_Direct_3(InputStr);
  SW.Stop;
  OutputDebugString(PChar('Direkter Zugriff 3: ' + SW.Elapsed.TotalMilliseconds.ToString + ' ms'));
end;

Geändert von TiGü (Gestern um 13:03 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 16:48 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz