Did some cleanup of the UI and code

This commit is contained in:
Frederik Jacobsen 2025-10-18 19:47:22 +02:00
parent 8fd2b05b4d
commit 14bad15f4a
7 changed files with 198 additions and 219 deletions

View File

@ -9,14 +9,12 @@ namespace XSDVisualiser.Desktop.Converters
{ {
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (value is Occurs occ) if (value is not Occurs occ) return null;
{
var min = occ.Min; var min = occ.Min;
var max = occ.MaxIsUnbounded ? "*" : occ.Max.ToString(culture); var max = occ.MaxIsUnbounded ? "*" : occ.Max.ToString(culture);
return $"[{min}..{max}]"; return $"[{min}..{max}]";
} }
return null;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException(); => throw new NotSupportedException();

View File

@ -19,7 +19,7 @@
</DockPanel> </DockPanel>
<!-- Body: left tree | splitter | right details --> <!-- Body: left tree | splitter | right details -->
<Grid Grid.Row="1" ColumnDefinitions="*,Auto,1.6*" RowDefinitions="*"> <Grid Grid.Row="1" ColumnDefinitions="2*,Auto,*" RowDefinitions="*">
<!-- Left: Tree --> <!-- Left: Tree -->
<Border Background="{DynamicResource PanelBackgroundBrush}" <Border Background="{DynamicResource PanelBackgroundBrush}"
BorderBrush="{DynamicResource PanelBorderBrush}" BorderBrush="{DynamicResource PanelBorderBrush}"
@ -30,11 +30,8 @@
<TreeDataTemplate DataType="{x:Type m:SchemaNode}" ItemsSource="{Binding Children}"> <TreeDataTemplate DataType="{x:Type m:SchemaNode}" ItemsSource="{Binding Children}">
<StackPanel> <StackPanel>
<!-- Connector line from parent to this node (hidden for roots where ContentModel is null) --> <!-- Connector line from parent to this node (hidden for roots where ContentModel is null) -->
<Grid Margin="0,6,0,2" IsVisible="{Binding ContentModel, Converter={x:Static ObjectConverters.IsNotNull}}"> <Grid Margin="0,8,8,2" IsVisible="{Binding ContentModel, Converter={x:Static ObjectConverters.IsNotNull}}"
<Grid.ColumnDefinitions> ColumnDefinitions="16,*">
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- leading small elbow --> <!-- leading small elbow -->
<Rectangle Grid.Column="0" Width="16" Height="2" Fill="{DynamicResource SeparatorBrush}" VerticalAlignment="Center"/> <Rectangle Grid.Column="0" Width="16" Height="2" Fill="{DynamicResource SeparatorBrush}" VerticalAlignment="Center"/>
<Grid Grid.Column="1"> <Grid Grid.Column="1">
@ -50,7 +47,7 @@
<Border CornerRadius="4" <Border CornerRadius="4"
BorderBrush="{Binding Cardinality, Converter={StaticResource OptionalToBrush}}" BorderBrush="{Binding Cardinality, Converter={StaticResource OptionalToBrush}}"
BorderThickness="{Binding Cardinality, Converter={StaticResource OptionalToThickness}}" BorderThickness="{Binding Cardinality, Converter={StaticResource OptionalToThickness}}"
Padding="8" Margin="0,0,0,6" Background="{DynamicResource PanelBackgroundBrush}"> Padding="8" Margin="0,0,8,6" Background="{DynamicResource PanelBackgroundBrush}">
<StackPanel Orientation="Vertical" Spacing="2"> <StackPanel Orientation="Vertical" Spacing="2">
<!-- Name on its own line, prominent --> <!-- Name on its own line, prominent -->
<TextBlock Text="{Binding Name}" FontWeight="SemiBold"/> <TextBlock Text="{Binding Name}" FontWeight="SemiBold"/>
@ -69,7 +66,7 @@
<!-- Optional: show content model as a subtle hint --> <!-- Optional: show content model as a subtle hint -->
<TextBlock Text="{Binding ContentModel, StringFormat=Model: {0}}" <TextBlock Text="{Binding ContentModel, StringFormat=Model: {0}}"
Foreground="{DynamicResource MutedTextBrush}"/> Foreground="{DynamicResource SubtleTextBrush}" FontWeight="SemiBold"/>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>
@ -95,116 +92,31 @@
<!-- General info --> <!-- General info -->
<Border BorderBrush="{DynamicResource PanelBorderBrush}" BorderThickness="1" CornerRadius="4" Padding="10"> <Border BorderBrush="{DynamicResource PanelBorderBrush}" BorderThickness="1" CornerRadius="4" Padding="10">
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*">
<TextBlock Text="Name:" FontWeight="Bold"/> <TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Margin="8,0,0,0" Text="{Binding Name}"/> <TextBlock Grid.Row="0" Grid.Column="1" Margin="8,0,0,0" Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Text="Type:" FontWeight="Bold"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="Built-in:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Grid.Row="1" Margin="8,0,0,0" Text="{Binding TypeName}"/> <TextBlock Grid.Row="1" Grid.Column="1" Margin="8,0,0,0" Text="{Binding BuiltInType}" FontWeight="Bold"/>
<TextBlock Grid.Row="2" Text="Built-in:" FontWeight="Bold"/> <TextBlock Grid.Row="2" Grid.Column="0" Text="Model:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Grid.Row="2" Margin="8,0,0,0" Text="{Binding BuiltInType}"/> <TextBlock Grid.Row="2" Grid.Column="1" Margin="8,0,0,0" Text="{Binding ContentModel}"/>
<TextBlock Grid.Row="3" Text="Namespace:" FontWeight="Bold"/> <TextBlock Grid.Row="3" Grid.Column="0" Text="Namespace:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Grid.Row="3" Margin="8,0,0,0" Text="{Binding Namespace}"/> <TextBlock Grid.Row="3" Grid.Column="1" Margin="8,0,0,0" Text="{Binding Namespace}"/>
<TextBlock Grid.Row="4" Text="Cardinality:" FontWeight="Bold"/> <TextBlock Grid.Row="4" Grid.Column="0" Text="Cardinality:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Grid.Row="4" Margin="8,0,0,0" Text="{Binding Cardinality, Converter={StaticResource CardinalityToLabel}}"/> <TextBlock Grid.Row="4" Grid.Column="1" Margin="8,0,0,0" Text="{Binding Cardinality, Converter={StaticResource CardinalityToLabel}}"/>
<TextBlock Grid.Column="2" Text="Model:" FontWeight="Bold"/> <TextBlock Grid.Row="5" Grid.Column="0" Text="Nillable:" FontWeight="Bold"/>
<TextBlock Grid.Column="3" Margin="8,0,0,0" Text="{Binding ContentModel}"/> <TextBlock Grid.Row="5" Grid.Column="1" Margin="8,0,0,0" Text="{Binding IsNillable}"/>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Type:" FontWeight="Bold"/>
<TextBlock Grid.Row="6" Grid.Column="1" Margin="8,0,0,0" Text="{Binding TypeName}"/>
<TextBlock Grid.Row="5" Text="Nillable:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Grid.Row="5" Margin="8,0,0,0" Text="{Binding IsNillable}"/>
</Grid> </Grid>
</Border> </Border>
<!-- Attributes -->
<StackPanel>
<TextBlock Text="Attributes" FontWeight="SemiBold"/>
<ItemsControl ItemsSource="{Binding Attributes}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="m:AttributeInfo">
<Border BorderBrush="{DynamicResource PanelBorderBrush}" BorderThickness="1" CornerRadius="4" Padding="8" Margin="0,6,0,0">
<StackPanel Spacing="8">
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto,Auto,Auto">
<TextBlock Text="Name:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Margin="8,0,0,0" Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Text="Use:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Grid.Row="1" Margin="8,0,0,0" Text="{Binding Use}"/>
<TextBlock Grid.Row="2" Text="Type:" FontWeight="Bold"/>
<TextBlock Grid.Column="1" Grid.Row="2" Margin="8,0,0,0" Text="{Binding TypeName}"/>
<TextBlock Grid.Column="2" Text="Built-in:" FontWeight="Bold"/>
<TextBlock Grid.Column="3" Margin="8,0,0,0" Text="{Binding BuiltInType}"/>
</Grid>
<StackPanel IsVisible="{Binding Constraints, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Constraints" FontWeight="SemiBold"/>
<StackPanel>
<TextBlock Text="Enumerations" FontWeight="SemiBold"/>
<ItemsControl ItemsSource="{Binding Constraints.Enumerations}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{Binding ., StringFormat=• {0}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel>
<TextBlock Text="Patterns" FontWeight="SemiBold"/>
<ItemsControl ItemsSource="{Binding Constraints.Patterns}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{Binding ., StringFormat=• {0}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel IsVisible="{Binding Constraints.Numeric, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Numeric bounds" FontWeight="SemiBold"/>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Constraints.Numeric.MinInclusive, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Min inclusive:" FontWeight="Bold"/>
<TextBlock Margin="8,0,0,0" Text="{Binding Constraints.Numeric.MinInclusive}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Constraints.Numeric.MaxInclusive, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Max inclusive:" FontWeight="Bold"/>
<TextBlock Margin="8,0,0,0" Text="{Binding Constraints.Numeric.MaxInclusive}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Constraints.Numeric.MinExclusive, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Min exclusive:" FontWeight="Bold"/>
<TextBlock Margin="8,0,0,0" Text="{Binding Constraints.Numeric.MinExclusive}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Constraints.Numeric.MaxExclusive, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Max exclusive:" FontWeight="Bold"/>
<TextBlock Margin="8,0,0,0" Text="{Binding Constraints.Numeric.MaxExclusive}"/>
</StackPanel>
</StackPanel>
<StackPanel IsVisible="{Binding Constraints.Length, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Length bounds" FontWeight="SemiBold"/>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Constraints.Length.LengthSpecified}">
<TextBlock Text="Length:" FontWeight="Bold"/>
<TextBlock Margin="8,0,0,0" Text="{Binding Constraints.Length.Length}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Constraints.Length.MinLengthSpecified}">
<TextBlock Text="Min length:" FontWeight="Bold"/>
<TextBlock Margin="8,0,0,0" Text="{Binding Constraints.Length.MinLength}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Constraints.Length.MaxLengthSpecified}">
<TextBlock Text="Max length:" FontWeight="Bold"/>
<TextBlock Margin="8,0,0,0" Text="{Binding Constraints.Length.MaxLength}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Constraints --> <!-- Constraints -->
<StackPanel> <StackPanel>
<TextBlock Text="Constraints" FontWeight="SemiBold"/> <TextBlock Text="Constraints" FontWeight="SemiBold"/>

View File

@ -3,8 +3,8 @@ using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using XSDVisualiser.Core;
using XSDVisualiser.Models; using XSDVisualiser.Models;
using XSDVisualiser.Parsing;
namespace XSDVisualiser.Desktop namespace XSDVisualiser.Desktop
{ {

View File

@ -12,7 +12,7 @@ namespace XSDVisualiser.Desktop
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
} }
public static AppBuilder BuildAvaloniaApp() => private static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>() AppBuilder.Configure<App>()
.UsePlatformDetect() .UsePlatformDetect()
.LogToTrace() .LogToTrace()

View File

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml; using System.Xml;
using System.Xml.Schema; using System.Xml.Schema;
using XSDVisualiser.Models; using XSDVisualiser.Models;
namespace XSDVisualiser.Parsing namespace XSDVisualiser.Core
{ {
public class XsdSchemaParser public class XsdSchemaParser
{ {
@ -16,16 +13,16 @@ namespace XSDVisualiser.Parsing
_set.XmlResolver = new XmlUrlResolver(); _set.XmlResolver = new XmlUrlResolver();
using var reader = XmlReader.Create(xsdPath, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }); using var reader = XmlReader.Create(xsdPath, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore });
var schema = XmlSchema.Read(reader, ValidationCallback); var schema = XmlSchema.Read(reader, ValidationCallback);
_set.Add(schema); _set.Add(schema!);
_set.CompilationSettings = new XmlSchemaCompilationSettings { EnableUpaCheck = true }; _set.CompilationSettings = new XmlSchemaCompilationSettings { EnableUpaCheck = true };
_set.Compile(); _set.Compile();
var model = new SchemaModel var model = new SchemaModel
{ {
TargetNamespace = schema.TargetNamespace TargetNamespace = schema!.TargetNamespace
}; };
foreach (XmlSchemaElement globalEl in _set.Schemas().Cast<XmlSchema>() foreach (var globalEl in _set.Schemas().Cast<XmlSchema>()
.SelectMany(s => s.Elements.Values.Cast<XmlSchemaElement>())) .SelectMany(s => s.Elements.Values.Cast<XmlSchemaElement>()))
{ {
var node = BuildNodeForElement(globalEl, parentContentModel: null); var node = BuildNodeForElement(globalEl, parentContentModel: null);
@ -58,8 +55,7 @@ namespace XSDVisualiser.Parsing
}; };
var type = ResolveElementType(element); var type = ResolveElementType(element);
if (type != null) if (type == null) return node;
{
node.TypeName = GetQualifiedTypeName(type); node.TypeName = GetQualifiedTypeName(type);
if (type.Datatype != null) if (type.Datatype != null)
{ {
@ -76,7 +72,6 @@ namespace XSDVisualiser.Parsing
node.Constraints = ExtractConstraints(st); node.Constraints = ExtractConstraints(st);
break; break;
} }
}
return node; return node;
} }
@ -84,7 +79,7 @@ namespace XSDVisualiser.Parsing
private static string? GetQualifiedTypeName(XmlSchemaType type) private static string? GetQualifiedTypeName(XmlSchemaType type)
{ {
if (!type.QualifiedName.IsEmpty) return type.QualifiedName.ToString(); if (!type.QualifiedName.IsEmpty) return type.QualifiedName.ToString();
return type.BaseXmlSchemaType != null && !type.BaseXmlSchemaType.QualifiedName.IsEmpty return type.BaseXmlSchemaType is { QualifiedName.IsEmpty: false }
? type.BaseXmlSchemaType.QualifiedName.ToString() ? type.BaseXmlSchemaType.QualifiedName.ToString()
: type.Name; : type.Name;
} }
@ -96,17 +91,72 @@ namespace XSDVisualiser.Parsing
{ {
return _set.GlobalTypes[el.SchemaTypeName] as XmlSchemaType; return _set.GlobalTypes[el.SchemaTypeName] as XmlSchemaType;
} }
if (el.SchemaType != null) return el.SchemaType;
return null; return el.SchemaType;
} }
private void HandleComplexType(SchemaNode node, XmlSchemaComplexType ct) private void HandleComplexType(SchemaNode node, XmlSchemaComplexType ct)
{ {
// Attributes // Collect attributes (ensure uniqueness)
foreach (XmlSchemaAttribute attr in ct.AttributeUses.Values.OfType<XmlSchemaAttribute>()) var seenAttrKeys = new HashSet<string>(StringComparer.Ordinal);
// 1) Compiled attribute uses (includes inherited/group attributes after Compile)
foreach (var attr in ct.AttributeUses.Values.OfType<XmlSchemaAttribute>())
{
var qn = attr.QualifiedName.IsEmpty ? attr.RefName : attr.QualifiedName;
var key = qn.ToString();
if (seenAttrKeys.Add(key))
{ {
node.Attributes.Add(ExtractAttribute(attr)); node.Attributes.Add(ExtractAttribute(attr));
} }
}
// 2) Uncompiled attributes directly on the type (fallback)
foreach (var a in ct.Attributes.OfType<XmlSchemaAttribute>())
{
var qn = a.QualifiedName.IsEmpty ? a.RefName : a.QualifiedName;
var key = qn.ToString();
if (seenAttrKeys.Add(key))
{
node.Attributes.Add(ExtractAttribute(a));
}
}
// 3) Attributes from complexContent extension/restriction
if (ct.ContentModel is XmlSchemaComplexContent cc)
{
switch (cc.Content)
{
case XmlSchemaComplexContentExtension cext:
{
foreach (var a in cext.Attributes.OfType<XmlSchemaAttribute>())
{
var qn = a.QualifiedName.IsEmpty ? a.RefName : a.QualifiedName;
var key = qn.ToString();
if (seenAttrKeys.Add(key))
{
node.Attributes.Add(ExtractAttribute(a));
}
}
break;
}
case XmlSchemaComplexContentRestriction cres:
{
foreach (var a in cres.Attributes.OfType<XmlSchemaAttribute>())
{
var qn = a.QualifiedName.IsEmpty ? a.RefName : a.QualifiedName;
var key = qn.ToString();
if (seenAttrKeys.Add(key))
{
node.Attributes.Add(ExtractAttribute(a));
}
}
break;
}
}
}
// Content model // Content model
if (ct.ContentTypeParticle is XmlSchemaGroupBase group) if (ct.ContentTypeParticle is XmlSchemaGroupBase group)
@ -158,7 +208,9 @@ namespace XSDVisualiser.Parsing
else if (ct.ContentType == XmlSchemaContentType.TextOnly && ct.ContentModel is XmlSchemaSimpleContent simpleContent) else if (ct.ContentType == XmlSchemaContentType.TextOnly && ct.ContentModel is XmlSchemaSimpleContent simpleContent)
{ {
node.ContentModel = "simple"; node.ContentModel = "simple";
if (simpleContent.Content is XmlSchemaSimpleContentExtension ext) switch (simpleContent.Content)
{
case XmlSchemaSimpleContentExtension ext:
{ {
var baseType = ResolveType(ext.BaseTypeName); var baseType = ResolveType(ext.BaseTypeName);
if (baseType is XmlSchemaSimpleType st) if (baseType is XmlSchemaSimpleType st)
@ -168,12 +220,19 @@ namespace XSDVisualiser.Parsing
node.BuiltInType ??= st.Datatype?.TypeCode.ToString(); node.BuiltInType ??= st.Datatype?.TypeCode.ToString();
} }
foreach (XmlSchemaAttribute attr in ext.Attributes.OfType<XmlSchemaAttribute>()) foreach (var attr in ext.Attributes.OfType<XmlSchemaAttribute>())
{
var qn = attr.QualifiedName.IsEmpty ? attr.RefName : attr.QualifiedName;
var key = qn.ToString();
if (seenAttrKeys.Add(key))
{ {
node.Attributes.Add(ExtractAttribute(attr)); node.Attributes.Add(ExtractAttribute(attr));
} }
} }
else if (simpleContent.Content is XmlSchemaSimpleContentRestriction res)
break;
}
case XmlSchemaSimpleContentRestriction res:
{ {
var baseType = ResolveType(res.BaseTypeName); var baseType = ResolveType(res.BaseTypeName);
if (baseType is XmlSchemaSimpleType st) if (baseType is XmlSchemaSimpleType st)
@ -184,6 +243,9 @@ namespace XSDVisualiser.Parsing
node.TypeName ??= GetQualifiedTypeName(st); node.TypeName ??= GetQualifiedTypeName(st);
node.BuiltInType ??= st.Datatype?.TypeCode.ToString(); node.BuiltInType ??= st.Datatype?.TypeCode.ToString();
} }
break;
}
} }
} }
} }
@ -208,12 +270,10 @@ namespace XSDVisualiser.Parsing
else if (!attr.SchemaTypeName.IsEmpty) st = ResolveType(attr.SchemaTypeName) as XmlSchemaSimpleType; else if (!attr.SchemaTypeName.IsEmpty) st = ResolveType(attr.SchemaTypeName) as XmlSchemaSimpleType;
else if (attr.SchemaType != null) st = attr.SchemaType; else if (attr.SchemaType != null) st = attr.SchemaType;
if (st != null) if (st == null) return info;
{
info.TypeName = GetQualifiedTypeName(st); info.TypeName = GetQualifiedTypeName(st);
info.BuiltInType = st.Datatype?.TypeCode.ToString(); info.BuiltInType = st.Datatype?.TypeCode.ToString();
info.Constraints = ExtractConstraints(st); info.Constraints = ExtractConstraints(st);
}
return info; return info;
} }
@ -225,11 +285,12 @@ namespace XSDVisualiser.Parsing
BaseTypeName = GetQualifiedTypeName(st.BaseXmlSchemaType) BaseTypeName = GetQualifiedTypeName(st.BaseXmlSchemaType)
}; };
if (st.Content is XmlSchemaSimpleTypeRestriction restr) switch (st.Content)
{ {
case XmlSchemaSimpleTypeRestriction restr:
MergeFacets(cons, restr.Facets); MergeFacets(cons, restr.Facets);
} break;
else if (st.Content is XmlSchemaSimpleTypeList list) case XmlSchemaSimpleTypeList list:
{ {
cons.Patterns.Add("(list)"); cons.Patterns.Add("(list)");
if (!list.ItemTypeName.IsEmpty) if (!list.ItemTypeName.IsEmpty)
@ -241,18 +302,23 @@ namespace XSDVisualiser.Parsing
Merge(cons, sub); Merge(cons, sub);
} }
} }
break;
} }
else if (st.Content is XmlSchemaSimpleTypeUnion union) case XmlSchemaSimpleTypeUnion union:
{ {
cons.Patterns.Add("(union)"); cons.Patterns.Add("(union)");
foreach (var memberType in union.BaseMemberTypes) foreach (var memberType in union.BaseMemberTypes)
{ {
if (memberType is XmlSchemaSimpleType mst) if (memberType is { } mst)
{ {
var sub = ExtractConstraints(mst); var sub = ExtractConstraints(mst);
Merge(cons, sub); Merge(cons, sub);
} }
} }
break;
}
} }
return cons; return cons;
@ -261,8 +327,8 @@ namespace XSDVisualiser.Parsing
private static void Merge(ConstraintSet target, ConstraintSet? source) private static void Merge(ConstraintSet target, ConstraintSet? source)
{ {
if (source == null) return; if (source == null) return;
foreach (var e in source.Enumerations) if (!target.Enumerations.Contains(e)) target.Enumerations.Add(e); foreach (var e in source.Enumerations.Where(e => !target.Enumerations.Contains(e))) target.Enumerations.Add(e);
foreach (var p in source.Patterns) if (!target.Patterns.Contains(p)) target.Patterns.Add(p); foreach (var p in source.Patterns.Where(p => !target.Patterns.Contains(p))) target.Patterns.Add(p);
if (source.Numeric != null) if (source.Numeric != null)
{ {
target.Numeric ??= new NumericBounds(); target.Numeric ??= new NumericBounds();
@ -271,8 +337,9 @@ namespace XSDVisualiser.Parsing
target.Numeric.MinExclusive ??= source.Numeric.MinExclusive; target.Numeric.MinExclusive ??= source.Numeric.MinExclusive;
target.Numeric.MaxExclusive ??= source.Numeric.MaxExclusive; target.Numeric.MaxExclusive ??= source.Numeric.MaxExclusive;
} }
if (source.Length != null)
{ if (source.Length == null) return;
target.Length ??= new LengthBounds(); target.Length ??= new LengthBounds();
if (source.Length.LengthSpecified && !target.Length.LengthSpecified) if (source.Length.LengthSpecified && !target.Length.LengthSpecified)
{ {
@ -284,13 +351,12 @@ namespace XSDVisualiser.Parsing
target.Length.MinLength = source.Length.MinLength; target.Length.MinLength = source.Length.MinLength;
target.Length.MinLengthSpecified = true; target.Length.MinLengthSpecified = true;
} }
if (source.Length.MaxLengthSpecified && !target.Length.MaxLengthSpecified)
{ if (!source.Length.MaxLengthSpecified || target.Length.MaxLengthSpecified) return;
target.Length.MaxLength = source.Length.MaxLength; target.Length.MaxLength = source.Length.MaxLength;
target.Length.MaxLengthSpecified = true; target.Length.MaxLengthSpecified = true;
} }
}
}
private static void MergeFacets(ConstraintSet cons, XmlSchemaObjectCollection facets) private static void MergeFacets(ConstraintSet cons, XmlSchemaObjectCollection facets)
{ {

View File

@ -1,4 +1,4 @@
using XSDVisualiser.Parsing; using XSDVisualiser.Core;
using XSDVisualiser.Utils; using XSDVisualiser.Utils;
/* /*
@ -80,6 +80,8 @@ catch (Exception ex)
Environment.Exit(3); Environment.Exit(3);
} }
return;
static void PrintHelp() static void PrintHelp()
{ {
Console.WriteLine("XSDVisualiser - Parse an XSD and emit a structural model with types, constraints, and cardinality"); Console.WriteLine("XSDVisualiser - Parse an XSD and emit a structural model with types, constraints, and cardinality");

View File

@ -8,6 +8,12 @@ namespace XSDVisualiser.Utils
{ {
public static class Serialization public static class Serialization
{ {
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};
public static string ToXml<T>(T obj) public static string ToXml<T>(T obj)
{ {
var serializer = new XmlSerializer(typeof(T)); var serializer = new XmlSerializer(typeof(T));
@ -20,12 +26,7 @@ namespace XSDVisualiser.Utils
public static string ToJson<T>(T obj) public static string ToJson<T>(T obj)
{ {
var options = new JsonSerializerOptions return JsonSerializer.Serialize(obj, JsonOptions);
{
WriteIndented = true,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};
return JsonSerializer.Serialize(obj, options);
} }
private sealed class Utf8StringWriter : StringWriter private sealed class Utf8StringWriter : StringWriter