-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for XSD nillable and xsi:nil="true" #60
Conversation
WIP: needs runtime support for setters
WIP: setting collections not supported yet
WIP: codgen must be modified to take advantage of new option
Fix derived XList not supporting XNil.Value properly
@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?). Otherwise we could enable Preview C# features in tests csproj; or rewrite the strings as verbatim or regular strings. |
…NET SDK to use." This reverts commit 9b1e83f.
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 ! |
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
.Note
Any combination is acceptable. An element can be optional but not nillable, required and nillable, or optional and nillable.
In all three instances, the apparent CLR type is nullable.
As we shall see, when writing XML linqtoxsd gives preference to
xsi:nil
when allowed.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 that
XNil.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 with
xsi: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 initializer
new 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.When
null
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.Note
Technically, an XML element could have
xsi:nil="true"
and children or attributes.This does not make much sense and simply maps to
null
in CLR.Setting a nillable element to
null
addsxsi:nil="true"
and remove any existing children or attributes.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.
Tip
Users that want a "cleaner" document can easily tweak that themselves. Just add:
element.Untyped.Add(new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"));
on the root element where you want
xsi
declared andXElement
serialization will reuse that.This is what I did in my unit tests for example (look at
XsiNilTests.cs
).