stephanstapel/ZUGFeRD-csharp

"Token StartElement in state Epilog would result in an invalid XML document" on InvoiceDescriptor.Save(stream, v21, Basic)

springy76 opened this issue · 6 comments

I just updated the NuGet package from 7.0.1 to 11.3.0 and a unit test (creating an InvoiceDescriptor from scratch) not having been touched for months (or years) now fails:

System.InvalidOperationException
  HResult=0x80131509
  Message=Token StartElement in state Epilog would result in an invalid XML document.
  Source=System.Private.Xml
  StackTrace:
   at System.Xml.XmlTextWriter.AutoComplete(Token token)
   at System.Xml.XmlTextWriter.WriteStartElement(String prefix, String localName, String ns)
   at s2industries.ZUGFeRD.ProfileAwareXmlTextWriter.WriteStartElement(String prefix, String localName, String ns, Profile profile)
   at s2industries.ZUGFeRD.ProfileAwareXmlTextWriter.WriteStartElement(String localName, Profile profile)
   at s2industries.ZUGFeRD.InvoiceDescriptor21Writer.Save(InvoiceDescriptor descriptor, Stream stream)
   at s2industries.ZUGFeRD.InvoiceDescriptor.Save(Stream stream, ZUGFeRDVersion version, Profile profile)
   at UnitTest

  This exception was originally thrown at this call stack:
    System.Xml.XmlTextWriter.AutoComplete(System.Xml.XmlTextWriter.Token)
    System.Xml.XmlTextWriter.WriteStartElement(string, string, string)
    s2industries.ZUGFeRD.ProfileAwareXmlTextWriter.WriteStartElement(string, string, string, s2industries.ZUGFeRD.Profile)
    s2industries.ZUGFeRD.ProfileAwareXmlTextWriter.WriteStartElement(string, s2industries.ZUGFeRD.Profile)
    s2industries.ZUGFeRD.InvoiceDescriptor21Writer.Save(s2industries.ZUGFeRD.InvoiceDescriptor, System.IO.Stream)
    s2industries.ZUGFeRD.InvoiceDescriptor.Save(System.IO.Stream, s2industries.ZUGFeRD.ZUGFeRDVersion, s2industries.ZUGFeRD.Profile)
    UnitTest

Hi @springy76,
it is really hard to guess what might have gone wrong based on the exception. Can you please provide your unit test (working without any dependencies) and the file that is generated?

Since the surrounding code goes through several layers of abstraction and transformation code I can't provide a simple sample, but I serialized the final InvoiceDescriptor to json. Note that TradeLineItems does not get deserialized, but I guess this is something you can fix easily since everything else looks good so far.

var jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true, Converters = { new JsonStringEnumConverter() { } } };
var json = JsonSerializer.Serialize(invoiceDescriptor, jsonSerializerOptions);
var rebuilt = JsonSerializer.Deserialize<InvoiceDescriptor>(json, jsonSerializerOptions);
var json2 = JsonSerializer.Serialize(rebuilt, jsonSerializerOptions);
Debug.Assert(json2 == json);

InvoiceDescriptor.zip

I'm sorry, but generating the file from the data you provided works without any problem. Please try:

var jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true, Converters = { new JsonStringEnumConverter() { } } };
var json = File.ReadAllText("InvoiceDescriptorSerialized.json");
InvoiceDescriptor rebuilt = JsonSerializer.Deserialize<InvoiceDescriptor>(json, jsonSerializerOptions);
rebuilt.Save("output.xml", ZUGFeRDVersion.Version21, Profile.Basic);

As said, currently all TradeLineItems get stripped/ignored during Deserialize - and they seem to be the error source when they exist. If you add those 2 lines to your code you'll see:

var json2 = JsonSerializer.Serialize(rebuilt, jsonSerializerOptions);
Debug.Assert(json2 == json); // not equal, entire TradLineItems-Subtree/-Array is empty

The answer seems to be quite simple:

The internal setter of trade line items prohibts creating them by the deserializer:

public List<TradeLineItem> TradeLineItems { get; internal set; } = new List<TradeLineItem>();

Working alone in my spare time on the project, I am happy to accept your sponsoring:
https://github.com/sponsors/stephanstapel

The internal setter of trade line items prohibts creating them by the deserializer:

Klarer Fall von "Softwareproblem, kann man nichts machen" .

Irgendwie hatte ich die völlig irre Annahme, dass das auch in Bezug auf andere Bugreports - völlig losgelöst von diesem Issue - von Hilfe wäre, wenn man einen InvoiceDescriptor nicht nur serialisieren, sondern auch verlustfrei deserialisieren kann.