mganss / XmlSchemaClassGenerator

Generate C# classes from XML Schema files
Apache License 2.0
614 stars 180 forks source link

Generate classes for AIXM 5.1 XML Schema #216

Closed florin141 closed 4 years ago

florin141 commented 4 years ago

I have the following AIXM 5.1 XML Schema and when I try to deserialize some XML I get this exception. Any idea on how to fix that?

I'm trying to replace the classed generated with the xsd.exe tool for this github repo aixm-bindings-sharp.

mganss commented 4 years ago

Which arguments do you use when generating code? Can you post the XML you're trying to deserialize?

florin141 commented 4 years ago

I am using this test to generate the classes. As for the XML that I'm trying to deserialize, I'm getting the same exception for all of these XML files.

You can clone this forked aixm-bindings-sharp repo and see for yourself. The 'XmlSchemaClassGenerator' branch has the classes generated by the above test and all the resulting namespace errors fixed (manually).

mganss commented 4 years ago

First off, I think the namespace errors occur because there is a C# namespace that has the same segment name "aixm" at different levels in the namespace hierarchy, i.e. there is both aixm.v5_1_1 and aero.aixm.schema._5_1_1.extensions.eur.adr. If you use e.g. aero.aixm.v5_1_1 it works.

The real problem, though, is related to #36, in particular limitation 1 mentioned by @John-Welch here: https://github.com/mganss/XmlSchemaClassGenerator/issues/36#issuecomment-426937573

florin141 commented 4 years ago

I no longer have the namespace errors, thanks to your observation.

florin141 commented 4 years ago

With respect to limitation 1 mentioned by @John-Welch, can you expand a bit on that? Like what needs to be implemented so that I can deserialize these files.

mganss commented 4 years ago

The problem is with a more complex hierarchy of substitutions. The aixm schema is the most complex in this regard I have encountered thus far (other ones in our test suite that use substitution groups are is24immotransfer and bpmn). Expanding on the example in #36, if you have this:

<element name="vehicleElement" type="test:vehicle" abstract="true" />
<element name="vehicleElement2" type="test:vehicle" abstract="true" />
<element name="car2" type="test:car" substitutionGroup="test:vehicleElement"/>
<element name="bus2" type="test:bus" substitutionGroup="test:vehicleElement2"/>

<element name="fleet">
  <complexType>
    <sequence>
      <element maxOccurs="unbounded" ref="test:vehicleElement"/>
      <element maxOccurs="unbounded" ref="test:vehicleElement2"/>
    </sequence>
  </complexType>
</element>

The current code in master puts an XmlElementAttribute for both car2 and bus2 on both vehicleElement and vehicleElement2, although car2 cannot be substituted for vehicleElement2 (likewise for bus2 and vehicleElement). This results in the exception you're getting:

The XML element '...' from namespace '...' is already present in the current scope.

Initially I thought that we could solve this by more correctly mapping the substituted elements but this only uncovers another problem: Some substituted elements have the same type so you'll end up with something like this (I made this up):

[XmlElement("car1", Type=typeof(Car), Namespace="...")]
[XmlElement("car2", Type=typeof(Car), Namespace="...")]
public Vehicle Car { get; set; }

This results in a different runtime exception ("please use XmlChoiceIdentifier" or similar) because the serializer wouldn't know which element to serialize into if it encounters an object of type Car.

So I came up with another possible solution: Every substituted element gets its own property. I have an implementation in the substitution branch that passes the current test suite and at first glance seems to work with aixm. I still had to massage the generated code a little:

Also, the example XML has wrong namespaces: replace "5.1" with "5.1.1".

One thing I've noticed perusing the deserialized AirportHeliportType is that the gml:id is null. No idea what's happening there.

Please check it out and report your findings.

florin141 commented 4 years ago

I think your implementation from substitution branch is working.

The only exception I noticed is the Id (gml:id) that is left null when deserializing. Also, I noticed that if I remove the namespace prefix from the XML file, the value is picked up correctly.

In rest, everything seems perfect. Great job!!!

mganss commented 4 years ago

I've added code to avoid the name clashes and make the gml:id work. The latter issue was because global attributes weren't handled properly.

I have also expanded the unit test to check de/serialization of the Aixm sample files but I'm getting this now when creating an XmlSerializer object:

System.PlatformNotSupportedException: 'Compiling JScript/CSharp scripts is not supported'

This is on .NET Core. My console app was .NET Framework. The error does occur there, too, if I target .NET Core. Do you see this, too?

florin141 commented 4 years ago

I'm only using .NET Core to generate the code, however the same error occurs when I try to create an XmlSerializer on .NET Core 3.1 and just for curiosity, I tried to create that object on .NET Core 2.2 and the same error appears (on .NET Core 1.1, I get like -2147483648 errors lol)

I'm using the generated code on a .NET Framework 4.6.2, so, I have no problem serializing/deserializing and now the gml:id is showing up.

Seems like that setting the Form property is key, as I was just testing some code from another schema that is build on top of AIXM 5.1.1 XML Schema. That schema has the form for attributes and for elements set to default (that is the attribute attributeFormDefault and elementFormDefault is not even added in the schema) resulting in all properties to be left null when I try to deserialize. I'll post the schema and a couple of examples today and you can get a better picture then.

One more thing I noticed is that a 'Name' property of a certain type is defined in an abstract class and that most derived classes also have 'Name' property but of a different type. I think that to avoid those conflicts, the name of the property in the derived class is now something like 'Name1'. Is that something you intended to do?

mganss commented 4 years ago

Seems like that setting the Form property is key, as I was just testing some code from another schema that is build on top of AIXM 5.1.1 XML Schema. That schema has the form for attributes and for elements set to default (that is the attribute attributeFormDefault and elementFormDefault is not even added in the schema) resulting in all properties to be left null when I try to deserialize. I'll post the schema and a couple of examples today and you can get a better picture then.

OK thanks. Feel free to open a separate issue for this.

One more thing I noticed is that a 'Name' property of a certain type is defined in an abstract class and that most derived classes also have 'Name' property but of a different type. I think that to avoid those conflicts, the name of the property in the derived class is now something like 'Name1'. Is that something you intended to do?

Yes, exactly.