Skip to content

Commit

Permalink
Add configuration option to serialize empty collections (fixes #490)
Browse files Browse the repository at this point in the history
  • Loading branch information
mganss committed Feb 19, 2024
1 parent 382aef6 commit bbef9ea
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 38 deletions.
5 changes: 4 additions & 1 deletion XmlSchemaClassGenerator.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ static int Main(string[] args)
var nameSubstituteFiles = new List<string>();
var unionCommonType = false;
var separateNamespaceHierarchy = false;
var serializeEmptyCollections = false;

var options = new OptionSet {
{ "h|help", "show this message and exit", v => showHelp = v != null },
Expand Down Expand Up @@ -158,6 +159,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
{ "es|enumAsString", "Use string instead of enum for enumeration", v => enumAsString = v != null },
{ "ca|commandArgs", "generate a comment with the exact command line arguments that were used to generate the source code (default is true)", v => generateCommandLineArgs = v != null },
{ "uc|unionCommonType", "generate a common type for unions if possible (default is false)", v => unionCommonType = v != null },
{ "ec|serializeEmptyCollections", "serialize empty collections (default is false)", v => serializeEmptyCollections = v != null },
};

var globsAndUris = options.Parse(args);
Expand Down Expand Up @@ -244,7 +246,8 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
UseArrayItemAttribute = useArrayItemAttribute,
EnumAsString = enumAsString,
MapUnionToWidestCommonType = unionCommonType,
SeparateNamespaceHierarchy = separateNamespaceHierarchy
SeparateNamespaceHierarchy = separateNamespaceHierarchy,
SerializeEmptyCollections = serializeEmptyCollections,
};

if (nameSubstituteMap.Any())
Expand Down
32 changes: 32 additions & 0 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,38 @@ public void TestArray()
SharedTestFunctions.TestSamples(Output, "Tableau.Array", TableauPattern);
}

[Fact, TestPriority(1)]
[UseCulture("en-US")]
public void TestSerializeEmptyCollection()
{
var output = new FileWatcherOutputWriter(Path.Combine("output", "Tableau.EmptyCollection"));
Compiler.Generate("Tableau.EmptyCollection", TableauPattern,
new Generator
{
OutputWriter = output,
EnableDataBinding = true,
CollectionSettersMode = CollectionSettersMode.Private,
SerializeEmptyCollections = true
});
SharedTestFunctions.TestSamples(Output, "Tableau.EmptyCollection", TableauPattern);
}

[Fact, TestPriority(1)]
[UseCulture("en-US")]
public void TestSerializeEmptyPublicCollection()
{
var output = new FileWatcherOutputWriter(Path.Combine("output", "Tableau.EmptyPublicCollection"));
Compiler.Generate("Tableau.EmptyPublicCollection", TableauPattern,
new Generator
{
OutputWriter = output,
EnableDataBinding = true,
CollectionSettersMode = CollectionSettersMode.Public,
SerializeEmptyCollections = true
});
SharedTestFunctions.TestSamples(Output, "Tableau.EmptyPublicCollection", TableauPattern);
}

[Fact, TestPriority(1)]
[UseCulture("en-US")]
public void TestDtsx()
Expand Down
6 changes: 6 additions & 0 deletions XmlSchemaClassGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,12 @@ public bool SeparateNamespaceHierarchy
set { _configuration.SeparateNamespaceHierarchy = value; }
}

public bool SerializeEmptyCollections
{
get { return _configuration.SerializeEmptyCollections; }
set { _configuration.SerializeEmptyCollections = value; }
}

public bool ValidationError { get; private set; }

static Generator()
Expand Down
5 changes: 5 additions & 0 deletions XmlSchemaClassGenerator/GeneratorConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,5 +343,10 @@ public void WriteLog(string message)
/// Separates namespace hierarchy by folder. Default is false.
/// </summary>
public bool SeparateNamespaceHierarchy { get; set; } = false;

/// <summary>
/// Determines whether empty collections should be serialized as empty elements. Default is false.
/// </summary>
public bool SerializeEmptyCollections { get; set; } = false;
}
}
83 changes: 46 additions & 37 deletions XmlSchemaClassGenerator/TypeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -869,56 +869,65 @@ [new CodeMethodReturnStatement(valueExpression)],
}
else if (isEnumerable && !IsRequired)
{
var listReference = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), Name);
var collectionType = Configuration.CollectionImplementationType ?? Configuration.CollectionType;
var countProperty = collectionType == typeof(Array) ? nameof(Array.Length) : nameof(List<int>.Count);
var countReference = new CodePropertyReferenceExpression(listReference, countProperty);
var notZeroExpression = new CodeBinaryOperatorExpression(countReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(0));
if (Configuration.CollectionSettersMode is CollectionSettersMode.PublicWithoutConstructorInitialization or CollectionSettersMode.Public or CollectionSettersMode.Init or CollectionSettersMode.InitWithoutConstructorInitialization)
{
var notNullExpression = new CodeBinaryOperatorExpression(listReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null));
notZeroExpression = new CodeBinaryOperatorExpression(notNullExpression, CodeBinaryOperatorType.BooleanAnd, notZeroExpression);
}
var returnStatement = new CodeMethodReturnStatement(notZeroExpression);
var canBeNull = Configuration.CollectionSettersMode is CollectionSettersMode.PublicWithoutConstructorInitialization or CollectionSettersMode.Public or CollectionSettersMode.Init or CollectionSettersMode.InitWithoutConstructorInitialization;

if (Configuration.UseShouldSerializePattern)
if (canBeNull || !Configuration.SerializeEmptyCollections)
{
var shouldSerializeMethod = new CodeMemberMethod
var listReference = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), Name);
var collectionType = Configuration.CollectionImplementationType ?? Configuration.CollectionType;
var countProperty = collectionType == typeof(Array) ? nameof(Array.Length) : nameof(List<int>.Count);
var countReference = new CodePropertyReferenceExpression(listReference, countProperty);
var notZeroExpression = new CodeBinaryOperatorExpression(countReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(0));
var returnExpression = notZeroExpression;

if (canBeNull)
{
Attributes = MemberAttributes.Public,
Name = "ShouldSerialize" + Name,
ReturnType = new CodeTypeReference(typeof(bool)),
Statements = { returnStatement }
};
var notNullExpression = new CodeBinaryOperatorExpression(listReference, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null));
var notNullOrEmptyExpression = new CodeBinaryOperatorExpression(notNullExpression, CodeBinaryOperatorType.BooleanAnd, notZeroExpression);
returnExpression = Configuration.SerializeEmptyCollections ? notNullExpression : notNullOrEmptyExpression;
}

Configuration.MemberVisitor(shouldSerializeMethod, this);
var returnStatement = new CodeMethodReturnStatement(returnExpression);

typeDeclaration.Members.Add(shouldSerializeMethod);
}
else
{
var specifiedProperty = new CodeMemberProperty
if (Configuration.UseShouldSerializePattern)
{
Type = TypeRef<bool>(),
Name = Name + Specified,
HasSet = false,
HasGet = true,
};
specifiedProperty.CustomAttributes.Add(ignoreAttribute);
if (Configuration.EntityFramework) { specifiedProperty.CustomAttributes.Add(notMappedAttribute); }
specifiedProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
var shouldSerializeMethod = new CodeMemberMethod
{
Attributes = MemberAttributes.Public,
Name = "ShouldSerialize" + Name,
ReturnType = new CodeTypeReference(typeof(bool)),
Statements = { returnStatement }
};

specifiedProperty.GetStatements.Add(returnStatement);
Configuration.MemberVisitor(shouldSerializeMethod, this);

var specifiedDocs = new DocumentationModel[] {
typeDeclaration.Members.Add(shouldSerializeMethod);
}
else
{
var specifiedProperty = new CodeMemberProperty
{
Type = TypeRef<bool>(),
Name = Name + Specified,
HasSet = false,
HasGet = true,
};
specifiedProperty.CustomAttributes.Add(ignoreAttribute);
if (Configuration.EntityFramework) { specifiedProperty.CustomAttributes.Add(notMappedAttribute); }
specifiedProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;

specifiedProperty.GetStatements.Add(returnStatement);

var specifiedDocs = new DocumentationModel[] {
new() { Language = English, Text = $"Gets a value indicating whether the {Name} collection is empty." },
new() { Language = German, Text = $"Ruft einen Wert ab, der angibt, ob die {Name}-Collection leer ist." }
};
specifiedProperty.Comments.AddRange(GetComments(specifiedDocs).ToArray());
specifiedProperty.Comments.AddRange(GetComments(specifiedDocs).ToArray());

Configuration.MemberVisitor(specifiedProperty, this);
Configuration.MemberVisitor(specifiedProperty, this);

typeDeclaration.Members.Add(specifiedProperty);
typeDeclaration.Members.Add(specifiedProperty);
}
}
}

Expand Down

0 comments on commit bbef9ea

Please sign in to comment.