Reputation: 13553
I have an XML schema .xsd file and generate my file with all the C# classes with the xsd.exe tool. If I have a sequence of elements within an XML tag, that would be represented in C# with an array. The fail is obvious. How can I generate Lists instead of arrays?
Instead of fixed size arrays in a class, I would like to use Lists.
Book [] books = new Book[someFixSize];
List<Book> books = new List<Book>();
I have seen some older (very old) questions about this, but none of them provided a satisfied solution :/
This is the latest useful hint: http://www.stefanbader.ch/xsdcsarr2l-exe-refactor-xsd-array-to-list/
Upvotes: 18
Views: 15010
Reputation: 741
Dan Field has a PowerShell script that takes an xsd.exe output class and turns its arrays into generic lists. This has worked well for me with a simple class, but I don't know how well it scales. I've pasted the script below. Call it from a command prompt like this:
"$(TargetFrameworkSDKToolsDirectory)xsd.exe" /c "$(ProjectDir)ImportedPartCanonical.xsd" "$(ProjectDir)ProjectCanonical.xsd" /n:Tallan.BT.PipelineComponents
powershell.exe -ExecutionPolicy Unrestricted -file "$(solutiondir)\PowerShellScripts\PostProcessXsdExe.ps1" ProjectCanonical.cs "$(SolutionDir)Tallan.BT.PipelineComponents\SerializedClasses\ProjectCanonical.cs"
See the link for the full explanation.
# Author: Dan Field ([email protected])
# posted on blog.tallan.com/2016/03/10/xsd-exe-arrays-and-specified
# Purpose: fix the 'specified' attribute and convert arrays to list from XSD.exe generated classes
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, Position=1)]
[string]$inputFile,
[Parameter(Mandatory=$true, Position=2)]
[string]$outputFile,
[switch]$DeleteInputFile
)
# Much faster than using Get-Content and/or Out-File/Set-Content
$writer = [System.IO.StreamWriter] $outputFile
$reader = [System.IO.StreamReader] $inputFile
# Used to track Specified properties
$setterDict = @{}
while (($line = $reader.ReadLine()) -ne $null)
{
$thisStart = $line.IndexOf("this.") # Will be used for
$brackets = $line.IndexOf("[]") # Indicates an array that will be converted to a Generic List
# Assume that any private field that contains "Specified" needs to be grabbed
if (($line.IndexOf("private") -gt -1) -and ($line.IndexOf("Specified") -gt -1))
{
# Get the field name
$varName = $line.Split("{' ', ';'}", [System.StringSplitOptions]::RemoveEmptyEntries)[-1]
# Use the field name as a key, minus the ending "Specified" portion, e.g. fieldNameSpecified -> fieldName
# The value in the dictionary will be added to setters on the main property, e.g. "this.fieldNameSpecified = true;"
$setterDict.Add($varName.Substring(0, $varName.IndexOf("Specified")), "this." + $varName + " = true;")
# Output the line as is
$writer.WriteLine($line)
}
# Find property setters that aren't for the *Specified properties
elseif (($thisStart -gt -1) -and ($line.IndexOf(" = value") -gt -1) -and ($line.IndexOf("Specified") -lt 0))
{
# Get the field name
$thisStart += 5
$varName = $line.Substring($thisStart, $line.IndexOf(' ', $thisStart) - $thisStart)
# see if there's a "Specified" property for this one
if ($setterDict.ContainsKey($varName) -eq $true)
{
# Set the Specified property whenever this property is set
$writer.WriteLine((' ' * ($thisStart - 5)) + $setterDict[$varName])
}
# Output the line itself
$writer.WriteLine($line)
}
elseif ($brackets -gt 0) # change to List<T>
{
$lineParts = $line.Split(' ')
foreach ($linePart in $lineParts)
{
if ($linePart.Contains("[]") -eq $true)
{
$writer.Write("System.Collections.Generic.List<" + $linePart.Replace("[]", "> "))
}
else
{
$writer.Write($linePart + " ")
}
}
$writer.WriteLine();
}
else # Just output the original line
{
$writer.WriteLine($line)
}
}
if ($DeleteInputFile -eq $true)
{
Remove-Item $inputFile
}
# Make sure the file gets fully written and clean up handles
$writer.Flush();
$writer.Dispose();
$reader.Dispose();
Upvotes: 2
Reputation: 1
I ran into the same problem recently. The only reason I wanted List<T> instead of T[] was because I wanted to add items to the array before sending a request to a web service.
I used the fact that xsd.exe generates a partial class. You can add your own partial class adding a constructor and an ADDT method that will use Array.Resize<T>() before assigning to the (new) last element.
There isn't any need to change the generated code or use another tool.
Upvotes: 0
Reputation: 4307
Try Xsd2Code
It generates lists instead of arrays. Unfortunately I couldn't get it to deserialize my code, but comparing it to the code generated by xsd it looked very similar.
Upvotes: 0
Reputation: 80
I run into the same problem trying to use the svcutil without having the contracts, for that reason I wrote the xsdcsarr2l tool. If you are interested I take the time and upload a newer version where at least the list variables are initialized automatically. On the other hand, the project is light enough, that you can take the source and improve it yourself by using the NRefactory classes.
Upvotes: 4
Reputation: 10055
Try using svcutil.exe
svcutil /o:myFile.cs /ct:System.Collections.Generic.List myXsd.xsd
Upvotes: 2