Closed jods4 closed 5 months ago
@mamift I see the CI build fails because I used a raw string literal in my added tests and it's a preview feature in the old CI dotnet SDK (v6 I believe).
That's quite convenient to write multi-line XML do you think you can upgrade the CI SDK to a newer release (v8?). It would be beneficial to have access to all new language features.
Otherwise we could enable Preview C# features in tests csproj; or rewrite the strings as verbatim or regular strings.
OK so it seems upgrading to the .NET 8 SDK was a bit more complicated than just increasing the version number. But I have got the Test project to build and run by setting lang version to preview.
Thanks @mamift ! Do you have a release planned?
Should be up now: https://www.nuget.org/packages/XObjectsCore/3.4.3
Awesome thanks! 🎉
Fixes #54
This is a larger change than I expected, here's a description of the changes:
Goal is to support
nillable="true"
in XSD andxsi:nil="true"
in XML. When an element hasxsi:nil="true"
then the exposed typed value isnull
(applies to reference types, nullable value types, and typed elements).XSD handling
Parsing XSD
nillable
attribute was already there!Metadata in
ClrPropertyInfo
has been extended:CanBeAbsent
indicates whether the XML element can be absent in document. This may happen becauseminOccurs="0"
or the element is part of a choice.IsNillable
indicates whether the XML element can havexsi:nil="true"
, i.e. the xsd containsnillable="true"
.IsNullable
was already there but has a more specific meaning now: it only indicates whether the CLR property type is nullable. This is the case when the elementCanBeAbsent
orIsNillable
.Code generation
There are four main situations to consider: scalar vs lists (repeated elements); get and set.
A new test case has been added that covers all cases, check this file for the generated C#: https://github.com/mamift/LinqToXsdCore/blob/662fae09ec17ade84ed4c9bd1b6d41fd3bd757b2/LinqToXsd.Schemas/Tests/Nil/NilTest.xsd.cs
Note that I've also updated the XML documentation comments. Occurence includes now a
nillable
keyword. "Regular expression" (not quite) indicates nillable elements with a<nil>
suffix.Scalars
Reading a scalar element is quite simple. An additional step has been added to check for
xsi:nil
and returnsnull
when present.Writing support is mostly a runtime thing. If the property is nillable, the call to
SetElement
and co. is not generated withvalue
but rather withvalue ?? XNil.Value
.XNil.Value
is a well-known singleton object that indicates to the runtime that we want to create an element withxsi:nil="true"
. Many changes have been made to ensure thatXNil.Value
was handled in every code path. This approach means that when both approaches are possible,xsi:nil
is generated instead of removing the element. This is simpler for the code generation and also the only way to insert nulls in lists as we shall see in the next section.Lists (repeated elements)
There is a very interesting consequence of
xsi:nil
for lists. Previously, lists never used nullable CLR types: missing elements where simply represented by an empty list. Now withxsi:nil
lists themselves are still non-nullable (rather: they might be empty) but they may contain null values!So the key change in code generation is that repeated nillable elements generate
List<Element?>
properties (but optional elements do not).The rest of the support happens at runtime in
XList
and its derived classes.XList
has a newSupportsXsiNil
boolean property that is initialized by codegen to ensure the returned list acceptsnull
elements (whenSupportsXsiNil
is left to its defaultfalse
value, then passingnull
values toXList
methods throws, like it does today). This property is set with an initializernew XList() { SupportsXsiNil = true }
. I did not add it to the ctor because it was quite tricky as derived classesXSimpleList
,XTypedList
andXTypedSubstitutedList
take variable number of arguments and sometimesparams
.Runtime
Most
xsi:nil
helpers have been put in the newXNil
class.XList
and its 3 derived classes have been largely rewritten so that whenSupportXsiNil = true
, they can contain and operate onnull
items.null
CLR items are translated into<Element xsi:nil="true">
(and vice-versa).All the core
XObject
methods that are involved in setting element values are modified to recognize the singleton objectXNil.Value
. This indicates that anxsi:nil
must be set on target element. Whennull
is passed to those methods instead they work as before by removing the element (which is still the mode of operation of properties that are notnillable
).When setting a non-null value, we must always remove
xsi:nil
, in case it was set before.No special care is given to declare the
xsi
namespace. So by default it turns out to be a local declaration on every nil element like:I thought of adding this declaration at the root of documents that may contain nillable elements, but it turns out that it's not easy to know when an element might be a root when manipulated by user code, nor to find a convenient place to modify the
XElement
. So I decided the XML was valid and left it like that.