XMLDocument in XmlWriter nimmt UTF-8 nicht an



  • Ja, das schon. Ich kann auch einfach InnerXml ausgeben, da steht es auch richtig. Aber wie krieg ich es hin, dass ich so ein Dokument richtig schön formatiert bekomme, ohne dass mir das Encoding flöten geht? InnerXml und dergleichen arbeiten ja komplett ohne Einrückungen.


  • Administrator

    Das Problem liegt wirklich am StringBuilder . Intern erstellt der XmlWriter aus dem StringBuilder einen StringWriter . Und der StringWriter hat ein Encoding von UTF-16. Der XmlWriter will alles korrekt machen und korrigiert daher dein falsch gesetzes Encoding , bzw. übernimmt einfach jenes vom StringWriter .

    Wie du es richtig machen kannst? Nimm keinen StringBuilder oder StringWriter . Wieso verwendest du keinen StreamWriter ? Wieso die Zwischenstation über den StringBuilder ? Beim StreamWriter kannst du das Encoding direkt angeben.

    Du könntest dann allenfalls einen MemoryStream verwenden, um das ganze als UTF-8 String im Speicher zu halten, falls du dies dann noch irgendwie weiterverarbeitest.

    var memoryStream = new MemoryStream();
    var textWriter = new StreamWriter(memoryStream, Encoding.UTF8);
    var xmlWriter = XmlWriter.Create(textWriter);
    // ...
    

    Grüssli



  • OK, danke. Aber wie komme ich jetzt an das formatierte XML als String ran, so dass ich es ausgeben kann?


  • Administrator

    Pauwle schrieb:

    OK, danke. Aber wie komme ich jetzt an das formatierte XML als String ran, so dass ich es ausgeben kann?

    Wohin willst du es denn ausgeben? Willst du es als UTF-8 in eine Datei schreiben? Dann verwende gar keinen MemoryStream , sondern direkt den richtigen Konstruktor von StreamWriter .

    Ich hatte in meinem vorherigen Beitrag schon mal gefragt: Wieso willst du den Umweg über den StringBuilder gehen? Wieso willst du unbedingt einen UTF-16 String haben, welcher XML mit einer falschen Kodierungsangabe (UTF-8) enthält?

    Grüssli



  • In der Praxis geht es um folgendes:

    Es handelt sich um eine Webservicefunktion, die als Rückgabewert einen String besitzt. In diesem String befindet sich von mir selbst definiertes und von der Webservicefunktion zusammengesetztes XML, in dem beliebige Informationen stehen.

    Die XML-Struktur wird in der Webservicefunktion mithilfe von XmlDocument und Konsorten zusammen gebaut. Am Ende wird einfach das InnerXml von diesem XmlDocument zurückgeliefert.

    InnerXml ist allerdings unformatiert. Damit der Rückgabestring auch gleich vernünftig eingerückt ist, jag ich XmlDocument stattdessen durch die XmlWriterSettings.

    Meine Rückgabe-XML ist definiert als UTF-8. Dass nun der .NET-String technisch gesehen Unicode ist, spielt ja für die rein logische Festlegung, dass ich XML liefere, das immer UTF-8-konform ist, keine Rolle. Der Benutzer, der den XML-String entgegen nimmt, soll einfach nur wissen: Das hier ist ein UTF-8-XML, auch wenn der Datentyp System.String aus dem .NET Framework intern immer mit Unicode arbeitet. Aber die dahinterliegende Technik soll den Benutzer ja nicht interessieren.
    Das Property XmlDocument.InnerXml beinhaltet ja auch die Codierungsdeklaration UTF-8, wenn ich die angegeben habe, obwohl es sich technisch um einen 16-Bit-String handelt.

    Deshalb brauche ich die Möglichkeit, den Inhalt von XmlDocument mit Einrückungen zu versehen und als einfachen String zurückgeben zu können, ohne das er meine per CreateXmlDeclaration gesetzte Codierung überschreibt.



  • Keine Idee?
    Es kann doch nicht sein, dass XMLs, die in einer Stringvariable zwischengespeichert werden, immer UTF-16 haben müssen, bloß weil der .NET-String zufällig Unicode ist. Die Möglichkeit, verschiedene Encodings in XML anzugeben, muss doch trotzdem gegeben sein. Und zwar auch ohne dass ich den Inhalt per Stream in eine Datei schreibe oder als Raw-Daten in einem Byte-Array halte.


  • Administrator

    Es leuchtet mir immer noch nicht ein, wieso du ein UTF-16 kodierter String als UTF-8 kodiert markieren möchtest. Aus dem gleichen Grund wird dies auch nicht von XmlWriter unterstützt.

    Deine Webservice-Funktion scheint meiner Meinung nach völlig falsch zu sein. String ist der falsche Rückgabewert. Die Funktion sollte einen Stream als Argument erhalten, in welchen das Resultat geschrieben werden kann. Oder irgendetwas in diese Richtung halt.

    Du kannst natürlich entsprechende Workarounds einsetzen. Z.B. kannst du ein String.Replace anwenden, um das Encoding im Resultat abzuändern. Was aber definitiv ein sehr fragwürdiges vorgehen ist.
    Du könntest auch von StringWriter erben und das Encoding Property überschreiben:

    class Utf8StringWriter
      : StringWriter
    {
      public override Encoding Encoding { get { return Encoding.UTF8; } }
    }
    

    Es gibt viele mögliche Workarounds. Aber sie sind alle eben ein wenig fragwürdig, wegen den oben genannten Gründen.

    Grüssli



  • Dravere schrieb:

    Es leuchtet mir immer noch nicht ein, wieso du ein UTF-16 kodierter String als UTF-8 kodiert markieren möchtest.

    Weil der XML-Inhalt tatsächlich nur Zeichen hat, die aus UTF-8 sind. Was interessiert es mich, welche Codierung der ins Framework eingebaute Datentyp string besitzt? Mein XML ist definiert als UTF-8. Die technische Tatsache, dass der .NET-String immer Unicode ist, hat doch überhaupt nichts mit meiner definitionsmäßigen Festlegung zu tun, dass meine XMLs UTF-8 codiert sind. Bloß weil sie in einem .NET-String zwischengespeichert werden, sollen sie doch nicht plötzlich in einer anderen Codierung deklariert werden.
    Wenn es nach der Logik geht, dann dürften XMLs, die in C# per String repräsentiert sind, nie, niemals, never eine andere Codierung als UTF-16 besitzen. Weil C# nur den Unicode-String hat.
    XML-Strings, die mit einem Programm erstellt wurden, das zufällig in C++ erstellt wurde, könnten dagegen UTF-8 oder UTF-16 sein.

    Du verstehst nicht, wieso ich einen UTF-16-String als UTF-8 deklariert haben will? Nun, ich verstehe nicht, wieso der Inhalt meines XML davon abhängig sein soll, wie der String-Datentyp der Programmiersprache, die ich zufällig verwende, technisch arbeitet.

    Dravere schrieb:

    Deine Webservice-Funktion scheint meiner Meinung nach völlig falsch zu sein. String ist der falsche Rückgabewert. Die Funktion sollte einen Stream als Argument erhalten, in welchen das Resultat geschrieben werden kann. Oder irgendetwas in diese Richtung halt.

    Wieso denn das? XML ist ein simples Textformat. Also sollte es auch möglich sein, den Inhalt als simplen Text zurückzugeben. Und dafür ist nunmal ein String die entsprechende Repräsentation. Da brauch ich keinen komplizierten Stream mit Read und Flush und Dispose. Könnte ein Webservice-Client, der nicht in .NET geschrieben ist, mit diesem Datentyp überhaupt anständig umgehen?

    Wenn String übrigens der falsche Typ für XML ist, wieso ist es dann möglich, dass XmlDocument die Funktion Load(string) besitzt? Und wieso kann ich eine Encodierung für ein XmlDocument festlegen und das Property InnerXml gibt mir das gesamte Markup als String zurück, mit dem von mit gesetzten Encoding (nicht mit dem Encoding der Klasse System.String)? Das Encoding wird komischerweise erst geändert, wenn ich Einrückungsfunktionen einbaue. Solange ich nur das nicht eingerückte InnerXml aus XmlDocument nehme haut die Codierung noch hin. (Nach deiner Logik sollte bereits InnerXml die Codierung UTF-16 aufweisen, weil InnerXml ja vom Typ string ist. Hier hast du also ein reales Beispiel, wo ein als .NET-String repräsentiertes XML-Markup als UTF-8 codiert ist.)



  • "XML-Strings, die mit einem Programm erstellt wurden, das zufällig in C++ erstellt wurde, könnten dagegen UTF-8 oder UTF-16 sein"
    ...das liegt aber nur daran, dass ein char in C/C++ eigentlich nicht wirklich ein Zeichen repräsentiert sondern eher einfach nur ein Byte.
    Ob aus char x=0x84 ein 'ä' oder ein 'Д' wird hängt da allein von der Funktion ab, die diesen "String" irgendwo ausgibt - Das ist bei C# nicht so.
    Bei Verwendung von wchar sieht das auch schon wieder anders aus.

    XmlDocument.LoadXml(string) - schlägt normalerweise ebenfalls fehl, sofern kein utf-16 im Header steht. Fehlt der Header ganz wird utf-16 einfach angenommen.
    Nach dem Parsen hat XmlDocument bereits alle Nodes in utf-16 umgewandelt.
    D.h. .InnerXml wird immer einen utf16-String zurückliefern.

    ----

    Wenn das Rückgabeformat deines Webservices ohnehin ein XmlDocument ist, kannst du da nicht direkt deine XML-Nodes einfügen?

    Ansonsten hier noch ne Variante, sofern du wirklich ein XML-Dokument in einem XML-Dokument verpacken willst:

    XmlDocument doc; // Das XmlDocument-Objekt
    string xml; // Hier kommt das Ergebnis rein
    
    using(MemoryStream ms=new MemoryStream())
    {
       doc.Save(ms); // In den Stream speichern
       byte[] xmlBytes=ms.ToArray(); // Stream-Inhalt als byte[]-Array holen
       xml=Encoding.UTF8.GetString(xmlBytes); // byte[]-Array mit utf8-Inhalt in String umwandeln
    }
    

  • Administrator

    Pauwle schrieb:

    Du verstehst nicht, wieso ich einen UTF-16-String als UTF-8 deklariert haben will? Nun, ich verstehe nicht, wieso der Inhalt meines XML davon abhängig sein soll, wie der String-Datentyp der Programmiersprache, die ich zufällig verwende, technisch arbeitet.

    Weil die Programmiersprache für solche Fälle andere Möglichkeiten anbietet. Weil ein String nunmal mit einer Encodierung zusammenhängt. Einen String kannst du nur mit einer Encodierung zusammen überhaupt haben. Alles andere ist einfach nur ein Bytestrom und dazu gibt es den Stream .

    Pauwle schrieb:

    Da brauch ich keinen komplizierten Stream mit Read und Flush und Dispose. Könnte ein Webservice-Client, der nicht in .NET geschrieben ist, mit diesem Datentyp überhaupt anständig umgehen?

    Ein Stream ist deutlich einfacher als ein String. Ein Stream bedeutet nur eine Aneinanderreihung von Bytes. Das ist alles. Ein String hat dagegen schon eine Encodierung mit dabei und hat allerhand zusätzlicher Funktionalität und Definition.

    Ein Stream ist das einfachste was du im Bereich Input/Output bekommen kannst. Darauf baut in .Net auch so gut wie jeder Input/Output auf. Wenn du deinen String irgendwann irgendwo ausgeben willst, wirst du sehr wahrscheinlich einen Stream benötigen. Deshalb sprach ich auch vom Umweg über den String, weil du auch direkt in diesen Stream schreiben könntest.

    Jede mir bekannte Programmiersprache verwendet irgendeine Art von Stream. std::basic_istream, std::basic_ostream; io.IOBase (Python); java.io.InputStream, java.io.OutputStream; FILE, fopen, fread, fwrite, fclose; IO Handle (Haskell) etc. nur um ein paar wenige zu nennen.

    Pauwle schrieb:

    Wenn String übrigens der falsche Typ für XML ist, ...

    Das war nicht meine Aussage. Meine Aussage war, dass deine Webservice-Funktion nicht einen String zurückgeben sollte. Wenn sie alles mögliche zurückgeben kann und vor allem keinen UTF-16 String zurück gibt, dann erscheint mir der .Net String der falsche Datentyp zu sein. Um alles mögliche zurückgeben zu können, verwendet man einen Stream.

    Einfaches Beispiel:
    Schau dir mal die HttpWebRequest Klasse an. Dort kannst du im Body alles mögliche mitgeben. Vielfach wird dort auch ein UTF-8 XML mitgegeben. Und wie übergibt man diesen Body? Mit einem Stream , den man sich von der Klasse holen kann.

    Schlussendlich läuft es aber sowieso auf das folgende hinaus:
    Du kannst entweder mit der Sprache und dem dazugehörigen Framework zusammenarbeiten oder gegen die Sprache und dem Framework vorgehen. Letzteres ist mühsam und nicht zielführend.

    Grüssli


Anmelden zum Antworten