Skip to content

Commit f459c29

Browse files
committed
Fix #592
1 parent f616511 commit f459c29

2 files changed

Lines changed: 125 additions & 9 deletions

File tree

XmlSchemaClassGenerator.Tests/XmlTests.cs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.CodeDom;
33
using System.Collections.Generic;
44
using System.Collections.ObjectModel;
@@ -2269,6 +2269,85 @@ public void DoNotGenerateSamePropertiesInDerivedInterfacesClassTest()
22692269

22702270
}
22712271

2272+
[Fact]
2273+
public void ChoiceWithDuplicateElementsInSequencesEmitsCorrectOrder()
2274+
{
2275+
const string xsd =
2276+
"""
2277+
<?xml version="1.0" encoding="UTF-8"?>
2278+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
2279+
elementFormDefault="qualified" attributeFormDefault="unqualified">
2280+
2281+
<xs:element name="Root">
2282+
<xs:complexType>
2283+
<xs:choice>
2284+
<xs:sequence>
2285+
<xs:element name="FirstChoiceElement1" type="xs:string"/>
2286+
<xs:element name="FirstChoiceElement2" type="xs:string"/>
2287+
<xs:element name="SharedElement1" type="xs:string"/>
2288+
<xs:element name="FirstChoiceElement3" type="xs:string"/>
2289+
<xs:element name="FirstChoiceElement4" type="xs:string"/>
2290+
<xs:element name="SharedElement2" type="xs:string"/>
2291+
<xs:element name="FirstChoiceElement5" type="xs:string"/>
2292+
<xs:element name="FirstChoiceElement6" type="xs:string"/>
2293+
</xs:sequence>
2294+
<xs:sequence>
2295+
<xs:element name="SecondChoiceElement1" type="xs:string"/>
2296+
<xs:element name="SharedElement1" type="xs:string"/>
2297+
<xs:element name="SecondChoiceElement2" type="xs:string"/>
2298+
<xs:element name="SharedElement2" type="xs:string"/>
2299+
<xs:element name="SecondChoiceElement3" type="xs:string"/>
2300+
</xs:sequence>
2301+
</xs:choice>
2302+
</xs:complexType>
2303+
</xs:element>
2304+
2305+
</xs:schema>
2306+
""";
2307+
string[] expectedPropertiesInOrder =
2308+
[
2309+
"FirstChoiceElement1",
2310+
"FirstChoiceElement2",
2311+
"SecondChoiceElement1",
2312+
"SharedElement1",
2313+
"FirstChoiceElement3",
2314+
"FirstChoiceElement4",
2315+
"SecondChoiceElement2",
2316+
"SharedElement2",
2317+
"FirstChoiceElement5",
2318+
"FirstChoiceElement6",
2319+
"SecondChoiceElement3",
2320+
];
2321+
var writer = new MemoryOutputWriter();
2322+
var gen = new Generator
2323+
{
2324+
OutputWriter = writer,
2325+
NamespaceProvider = new NamespaceProvider
2326+
{
2327+
GenerateNamespace = key => "Test"
2328+
},
2329+
AssemblyVisible = true,
2330+
EmitOrder = true,
2331+
// xs:choice branches sharing the same xs:element name violate UPA (Unique Particle Attribution),
2332+
EnableUpaCheck = false
2333+
};
2334+
gen.Generate([new StringReader(xsd)]);
2335+
2336+
var content = Assert.Single(writer.Content);
2337+
2338+
var assembly = Compiler.Compile(nameof(ChoiceWithDuplicateElementsInSequencesEmitsCorrectOrder), content);
2339+
2340+
var rootType = assembly.GetType("Test.Root");
2341+
Assert.NotNull(rootType);
2342+
2343+
var actualPropertiesInOrder = rootType.GetProperties()
2344+
.Where(property => Attribute.IsDefined(property, typeof(XmlElementAttribute)))
2345+
.OrderBy(property => property.GetCustomAttribute<XmlElementAttribute>().Order)
2346+
.Select(property => property.Name)
2347+
.ToArray();
2348+
Assert.Equal(expectedPropertiesInOrder, actualPropertiesInOrder);
2349+
}
2350+
22722351
[Fact]
22732352
public void NillableWithDefaultValueTest()
22742353
{

XmlSchemaClassGenerator/ModelBuilder.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -985,9 +985,22 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
985985
Substitute substitute = null, int order = 0, bool passProperties = true)
986986
{
987987
var properties = new List<PropertyModel>();
988+
var initialOrder = order;
989+
XmlSchemaObject prevParent = null;
990+
int batchStart = -1;
988991

989992
foreach (var item in items)
990993
{
994+
// Track parent changes to detect transitions between choice branches.
995+
// When elements from a new branch are encountered, batchStart marks where
996+
// the new branch's properties begin in the list.
997+
if (item.XmlParent != prevParent)
998+
{
999+
if (prevParent != null)
1000+
batchStart = properties.Count;
1001+
prevParent = item.XmlParent;
1002+
}
1003+
9911004
PropertyModel property = null;
9921005

9931006
switch (item.XmlParticle)
@@ -1023,20 +1036,44 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
10231036
// Discard duplicate property names. This is most likely due to:
10241037
// - Choice or
10251038
// - Element and attribute with the same name
1026-
if (property != null && !properties.Exists(p => p.Name == property.Name))
1039+
if (property != null)
10271040
{
1028-
var itemDocs = GetDocumentation(item.XmlParticle);
1029-
property.Documentation.AddRange(itemDocs);
1030-
1031-
if (_configuration.EmitOrder)
1032-
property.Order = order++;
1041+
var existingIndex = properties.FindIndex(p => p.Name == property.Name);
1042+
if (existingIndex >= 0)
1043+
{
1044+
// Duplicate found in a choice branch - move the current batch of
1045+
// new-branch properties to just before the duplicate's position
1046+
// so that the interleaved order across branches is preserved.
1047+
if (batchStart >= 0 && batchStart < properties.Count)
1048+
{
1049+
var count = properties.Count - batchStart;
1050+
var segment = properties.GetRange(batchStart, count);
1051+
properties.RemoveRange(batchStart, count);
1052+
var adjustedIndex = existingIndex >= batchStart ? existingIndex - count : existingIndex;
1053+
properties.InsertRange(adjustedIndex, segment);
1054+
}
1055+
batchStart = properties.Count;
1056+
}
1057+
else
1058+
{
1059+
var itemDocs = GetDocumentation(item.XmlParticle);
1060+
property.Documentation.AddRange(itemDocs);
10331061

1034-
property.IsDeprecated = itemDocs.Exists(d => d.Text.StartsWith("DEPRECATED"));
1062+
property.IsDeprecated = itemDocs.Exists(d => d.Text.StartsWith("DEPRECATED"));
10351063

1036-
properties.Add(property);
1064+
properties.Add(property);
1065+
}
10371066
}
10381067
}
10391068

1069+
// Reassign Order values based on final property positions,
1070+
// accounting for any reordering due to choice branch interleaving.
1071+
if (_configuration.EmitOrder)
1072+
{
1073+
for (var i = 0; i < properties.Count; i++)
1074+
properties[i].Order = initialOrder + i;
1075+
}
1076+
10401077
return properties;
10411078
}
10421079

0 commit comments

Comments
 (0)