Jak načíst XML soubor s kódováním Windows-1250?

Autor Téma: Jak načíst XML soubor s kódováním Windows-1250?  (Přečteno 367 krát)

Offline vandrovnik

  • Hrdina
  • ****
  • Příspěvků: 447
  • Karma: 36
    • Verze Delphi: 10.2
Dobrý den, mám zhruba tento kód:

Kód: Delphi [Vybrat]
  1. uses ... XML.XMLDoc, XML.XMLDom, Xml.XmlIntf, XML.OmniXmlDom...
  2. ...
  3. var XmlData: IXmlDocument;
  4.     Zdroj: tMemoryStream;
  5. ...
  6. XmlData:=tXmlDocument.Create(nil);
  7. XmlData.LoadFromStream(Zdroj);
  8.  
Funguje to dobře, pokud je xml soubor v utf8. Bohužel ale pokud je soubor např. v kódování Windows-1250, tak LoadFromStream vyhodí výjimku Project xxxxx.exe raised exception class EEncodingError with message 'No mapping for the Unicode character exists in the target multi-byte code page'. (Přikládám XML soubor - validací projde, resp. hlásí to něco snad nedůležitého.)

Jde to nějak jednoduše vyřešit? Bláhově jsem se domníval, že "se to udělá nějak samo". Používám Delphi 10.2.3 Pro.

Díky,

Karel

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2172
  • Karma: 116
    • Verze Delphi: D2007, XE3, DX10
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #1 kdy: 15-05-2018, 10:55:56 »
Jde to nějak jednoduše vyřešit? Bláhově jsem se domníval, že "se to udělá nějak samo". Používám Delphi 10.2.3 Pro.
Ted si zrovna nedokazu vybavit, jestli jsem to nekdy cetl ze streamu a nebo jenom pomoci LoadFromXML, ale rekl bych, ze to normalne umelo dekodovat soubory podle hlavicky v XML.

Zkus tomu LoadFromStream() predhodit EncodingType jako parametr http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/XMLIntf_IXMLDocument_LoadFromStream.html, ale je to pritazene za vlasy, proc by ses mel divat do souboru, abys mohl specifikovat encoding. Podle popisu by si to melo to encoding vzit z XML.
« Poslední změna: 15-05-2018, 10:57:37 od pf1957 »

Offline vandrovnik

  • Hrdina
  • ****
  • Příspěvků: 447
  • Karma: 36
    • Verze Delphi: 10.2
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #2 kdy: 17-05-2018, 15:27:14 »
Jako EncodingType se tam dá předhodit jedině xetUTF_8Like, ale tím si bohužel nepomůžu, končí to úplně stejně. Ono to vůbec vypadá, že detekci Encoding zmiňují v dokumentaci...

    //load document from file
    // if aForceEncoding = nil: in encoding specified by the document
    // if aForceEncoding<>nil : enforce encoding (<?xml encoding=".."?> is ignored)

... ale jaksi ji zapomněli napsat - v XML.Internal.OmniXML mají natvrdo:

XTS := OmniTXMLTextStream.Create(Stream, smRead, TEncoding.UTF8, False);

procedure TOTextReader.InitStream(const aStream: TStream; const aDefaultEncoding: TEncoding);
- ta je už volána s aDefaultEncoding.Classname=TUTF8Encoding

Otázkou je, jestli má smysl to reportovat, nebo se tím stejně nikdo v nejbližších 10 letech nebude zabývat :/

Offline vandrovnik

  • Hrdina
  • ****
  • Příspěvků: 447
  • Karma: 36
    • Verze Delphi: 10.2
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #3 kdy: 17-05-2018, 16:03:39 »
Tak když místo Xml.omnixmldom.pas použiju Xml.Win.msxmldom.pas (a přidám ještě Xml.Win.msxmldom.MSXMLDOMDocumentFactory.AddDOMProperty('ProhibitDTD', False)), tak se dokument načte normálně. Tak to reportovat zkusím.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2172
  • Karma: 116
    • Verze Delphi: D2007, XE3, DX10
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #4 kdy: 17-05-2018, 16:06:10 »
... ale jaksi ji zapomněli napsat - v XML.Internal.OmniXML mají natvrdo:
[...]
Otázkou je, jestli má smysl to reportovat, nebo se tím stejně nikdo v nejbližších 10 letech nebude zabývat :/
OmniXXX - to ma urcite neco spolecnyho s Ondrejem Pokornym a ten tu nedavno neco komentoval, tak treba se k tomu postavi celem ;-)

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2172
  • Karma: 116
    • Verze Delphi: D2007, XE3, DX10
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #5 kdy: 17-05-2018, 16:07:50 »
Tak když místo Xml.omnixmldom.pas použiju Xml.Win.msxmldom.pas
Ten MS DOM model, to byvalo default reseni. To to zmenili nebo jsi chtel byt inovativni?

Offline vandrovnik

  • Hrdina
  • ****
  • Příspěvků: 447
  • Karma: 36
    • Verze Delphi: 10.2
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #6 kdy: 17-05-2018, 16:10:43 »
Ten MS DOM model, to byvalo default reseni. To to zmenili nebo jsi chtel byt inovativni?

Chtěl jsem být multiplatformní :-) Navíc mám mlhavý dojem, že mi s tím MS DOM kdysi něco haprovalo, co pro změnu fungovalo v Omni dobře (myslím, že při intenzivní práci s XML ve více vláknech spolehlivé pády aplikace).

Offline Ondřej Pokorný

  • Guru
  • *****
  • Příspěvků: 775
  • Karma: 55
    • Verze Delphi: Primárně Lazarus, jinak D7 až aktuální
    • Kluug.net
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #7 kdy: 18-05-2018, 07:15:39 »
Je to prastarý bug, který v Delphi není opraven. Problém je, že parser načítá XML po blocích a začne vždycky v UTF-8. No a když ten první blok má neplatné znaky, tak to spadne. Když ne, parsuje se, najde se "<?xml", no a teprv pak se změní kódování na ANSI.

Oprava je v Xml.Internal.OTextReadWrite.pas:
Kód: Delphi [Vybrat]
  1. procedure TOTextReader.LoadStringFromStream;
  2. var
  3.   xBuffer: TBytes;
  4.   xUTF8Inc: Integer;
  5.   xReadBytes: NativeInt;
  6.   xLen: Integer;
  7. const
  8.   BS = TEncodingBuffer_FirstElement;
  9. begin
  10.   xReadBytes := fStreamSize-fStreamPosition;
  11.   if xReadBytes > fBufferSize then
  12.     xReadBytes := fBufferSize;
  13.   if xReadBytes = 0 then
  14.     Exit;
  15.  
  16.   SetLength(xBuffer, xReadBytes+5);//5 is maximum UTF-8 increment
  17.   fStream.ReadBuffer(xBuffer[BS], xReadBytes);
  18.   if fEncoding is TUTF8Encoding then begin
  19.     //check if we did not reach an utf-8 character in the middle
  20.     if
  21.      ((Ord(xBuffer[BS+xReadBytes-1]) and $80) = $00)
  22.     then//last byte is 0.......
  23.       xUTF8Inc := 0
  24.     else if
  25.      ((xReadBytes > 1) and ((Ord(xBuffer[BS+xReadBytes-1]) and $E0) = $C0)) or//110..... -> double char
  26.      ((xReadBytes > 2) and ((Ord(xBuffer[BS+xReadBytes-2]) and $F0) = $E0)) or//1110.... -> triple char
  27.      ((xReadBytes > 3) and ((Ord(xBuffer[BS+xReadBytes-3]) and $F8) = $F0)) or//11110... -> 4 char
  28.      ((xReadBytes > 4) and ((Ord(xBuffer[BS+xReadBytes-4]) and $FC) = $F8)) or//111110.. -> 5 char
  29.      ((xReadBytes > 5) and ((Ord(xBuffer[BS+xReadBytes-5]) and $FE) = $FC))   //1111110. -> 6 char
  30.     then
  31.       xUTF8Inc := 1
  32.     else if
  33.      ((xReadBytes > 1) and ((Ord(xBuffer[BS+xReadBytes-1]) and $F0) = $E0)) or//1110.... -> triple char
  34.      ((xReadBytes > 2) and ((Ord(xBuffer[BS+xReadBytes-2]) and $F8) = $F0)) or//11110... -> 4 char
  35.      ((xReadBytes > 3) and ((Ord(xBuffer[BS+xReadBytes-3]) and $FC) = $F8)) or//111110.. -> 5 char
  36.      ((xReadBytes > 4) and ((Ord(xBuffer[BS+xReadBytes-4]) and $FE) = $FC))   //1111110. -> 6 char
  37.     then
  38.       xUTF8Inc := 2
  39.     else if
  40.      ((xReadBytes > 1) and ((Ord(xBuffer[BS+xReadBytes-1]) and $F8) = $F0)) or//11110... -> 4 char
  41.      ((xReadBytes > 2) and ((Ord(xBuffer[BS+xReadBytes-2]) and $FC) = $F8)) or//111110.. -> 5 char
  42.      ((xReadBytes > 3) and ((Ord(xBuffer[BS+xReadBytes-3]) and $FE) = $FC))   //1111110. -> 6 char
  43.     then
  44.       xUTF8Inc := 3
  45.     else if
  46.      ((xReadBytes > 1) and ((Ord(xBuffer[BS+xReadBytes-1]) and $FC) = $F8)) or//111110.. -> 5 char
  47.      ((xReadBytes > 2) and ((Ord(xBuffer[BS+xReadBytes-2]) and $FE) = $FC))   //1111110. -> 6 char
  48.     then
  49.       xUTF8Inc := 4
  50.     else if
  51.      ((xReadBytes > 1) and ((Ord(xBuffer[BS+xReadBytes-1]) and $FE) = $FC))   //1111110. -> 6 char
  52.     then
  53.       xUTF8Inc := 5
  54.     else
  55.       xUTF8Inc := 0;//ERROR ?
  56.  
  57.     if xUTF8Inc > 0 then
  58.       fStream.ReadBuffer(xBuffer[BS+xReadBytes], xUTF8Inc);
  59.   end else
  60.     xUTF8Inc := 0;
  61.  
  62.   Inc(fStreamPosition, xReadBytes+xUTF8Inc);
  63.   SetLength(xBuffer, xReadBytes+xUTF8Inc);
  64.  
  65.   if Length(xBuffer)>0 then
  66.   begin
  67.     xLen := fEncoding.GetCharCount(xBuffer, BS, Length(xBuffer));
  68.     if xLen = 0 then // default is UTF-8 -> if BOM not set and codepage is some ANSI, UTF-8 returns empty string -> try ASCII and read codepage from <?xml?> tag afterwards
  69.     begin
  70.       xLen := TEncoding.ASCII.GetCharCount(xBuffer, BS, Length(xBuffer));
  71.       fTempString := TEncoding.ASCII.GetString(xBuffer);
  72.     end else
  73.     begin
  74.       SetLength(fTempString, xLen);
  75.       fTempString := fEncoding.GetString(xBuffer);
  76.     end;
  77.   end;
  78.  
  79.   fTempStringLength := Length(fTempString);
  80.   fTempStringRemain := fTempStringLength;
  81.   fTempStringPosition := 1;
  82. end;
Embarcadero Technology Partner, juj. Člen Lazarus týmu, oj.

Offline vandrovnik

  • Hrdina
  • ****
  • Příspěvků: 447
  • Karma: 36
    • Verze Delphi: 10.2
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #8 kdy: 18-05-2018, 10:55:50 »
Je to prastarý bug, který v Delphi není opraven. Problém je, že parser načítá XML po blocích a začne vždycky v UTF-8. No a když ten první blok má neplatné znaky, tak to spadne. Když ne, parsuje se, najde se "<?xml", no a teprv pak se změní kódování na ANSI.

Moc děkuju, vyzkouším.
Nechceš jim to případně dát k mému reportu jako řešení?
https://quality.embarcadero.com/browse/RSP-20570

Díky,

Karel


Offline Ondřej Pokorný

  • Guru
  • *****
  • Příspěvků: 775
  • Karma: 55
    • Verze Delphi: Primárně Lazarus, jinak D7 až aktuální
    • Kluug.net
Re:Jak načíst XML soubor s kódováním Windows-1250?
« Odpověď #9 kdy: 18-05-2018, 11:13:19 »
Klidně jim to tam pošli. Ale je to takový jen takový jednoduchý workaround. Pro úplnost by se ten ASCII pokus měl konat jen dokud se nezačne číst samotný XML dokument, což teď není zajištěno.
Embarcadero Technology Partner, juj. Člen Lazarus týmu, oj.

 

S rychlou odpovědí můžete používat BB kódy a emotikony jako v běžném okně pro odpověď, ale daleko rychleji.

Jméno: E-mail:
Ověření:
Datový typ v Delphi, který má True a False: