feat: add typed production settings contract

This commit is contained in:
codex
2026-08-07 14:23:34 +03:00
parent 9a41d831b4
commit 652515c2ba
2 changed files with 101 additions and 0 deletions
@@ -0,0 +1,63 @@
using System.Globalization;
using System.Xml.Linq;
namespace PRecAnnot.Core;
/// <summary>Typed projection of the release Settings.xml sections consumed by production workflows.</summary>
public sealed record ProductionSettingsContract(
IReadOnlyList<NamedDrawingObject> CopyMissingObjects,
IReadOnlyList<LayerSetupRule> LayerSetup,
string AnnotationLayer,
string AnnotationTextStyle,
string AnnotationLeaderStyle,
string ConnectionPolylineLayer,
string ConnectionTextLayer,
string ConnectionTextStyle,
string ElevationLayer,
string ElevationTextStyle,
string SetLayer,
double? FixedSupportPlusLength,
string? ScriptFolder)
{
public static ProductionSettingsContract Parse(string xml)
{
var root = XElement.Parse(xml, LoadOptions.PreserveWhitespace);
var copy = root.Element("CopyMissingObjects")?.Elements("Object")
.Select(value => new NamedDrawingObject(Required(value, "type"), Required(value, "name")))
.ToArray() ?? Array.Empty<NamedDrawingObject>();
var layers = root.Element("LayersSetup")?.Elements("LayerSetup")
.Select(value => new LayerSetupRule(
Required(value, "filtertype"), Required(value, "filtervalue", allowEmpty: true),
ParseInt(value, "lineweight"), Required(value, "linetype", allowEmpty: true),
Required(value, "color", allowEmpty: true)))
.ToArray() ?? Array.Empty<LayerSetupRule>();
var annotations = root.Element("Annotations");
var connections = root.Element("LinenumberConnections");
var elevations = root.Element("Elevations");
var setLayer = root.Element("SetLayer")?.Attribute("name")?.Value;
if (string.IsNullOrWhiteSpace(setLayer)) throw Invalid("SetLayer.name");
var plusLength = root.Element("PlusLengthNO")?.Attribute("value")?.Value;
double? parsedPlusLength = double.TryParse(plusLength, NumberStyles.Float, CultureInfo.InvariantCulture, out var plus) ? plus : null;
return new(
copy,
layers,
Required(annotations, "layer"),
Required(annotations, "textstyle"),
Required(annotations, "multileaderstyle"),
Required(connections, "polylinelayer"),
Required(connections, "textlayer"),
Required(connections, "textstyle"),
Required(elevations, "layer"),
Required(elevations, "textstyle"),
setLayer,
parsedPlusLength,
root.Element("ScriptFolder")?.Attribute("path")?.Value);
}
private static int? ParseInt(XElement value, string name) => int.TryParse(value.Attribute(name)?.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed) ? parsed : null;
private static string Required(XElement? value, string name, bool allowEmpty = false) => value?.Attribute(name) is { Value: var result } && (allowEmpty || !string.IsNullOrWhiteSpace(result)) ? result : throw Invalid(name);
private static InvalidDataException Invalid(string field) => new("PRODUCTION_SETTINGS_INVALID:" + field);
}
public sealed record NamedDrawingObject(string Type, string Name);
public sealed record LayerSetupRule(string FilterType, string FilterValue, int? LineWeight, string LineType, string Color);
@@ -0,0 +1,38 @@
using PRecAnnot.Core;
namespace PRecAnnot.Core.Tests;
public sealed class ProductionSettingsContractTests
{
[Fact]
public void ParsesReleaseSectionsAndPreservesAssetNames()
{
const string xml = """
<Main>
<CopyMissingObjects><Object type="Block" name="BLOCK_ELEVATION"/><Object type="Layer" name="TEXT"/></CopyMissingObjects>
<PlusLengthNO value="1.5"/><ScriptFolder path="C:\PlantScripts"/>
<LayersSetup><LayerSetup filtertype="Status" filtervalue="New" lineweight="60" linetype="Continuous" color=""/></LayersSetup>
<Annotations layer="TEXT" textstyle="GOSTC" multileaderstyle="PXP"/>
<LinenumberConnections polylinelayer="AXIS" textlayer="TEXT" textstyle="GOSTC"/>
<Elevations layer="THIN" textstyle="GOSTC"/><SetLayer name="AXIS"/>
</Main>
""";
var result = ProductionSettingsContract.Parse(xml);
Assert.Equal("BLOCK_ELEVATION", result.CopyMissingObjects[0].Name);
Assert.Equal(60, result.LayerSetup[0].LineWeight);
Assert.Equal("PXP", result.AnnotationLeaderStyle);
Assert.Equal("AXIS", result.ConnectionPolylineLayer);
Assert.Equal(1.5, result.FixedSupportPlusLength);
Assert.Equal("C:\\PlantScripts", result.ScriptFolder);
}
[Fact]
public void RejectsMissingRequiredSection()
{
const string xml = "<Main><SetLayer name=\"AXIS\"/></Main>";
var exception = Assert.Throws<InvalidDataException>(() => ProductionSettingsContract.Parse(xml));
Assert.Equal("PRODUCTION_SETTINGS_INVALID:layer", exception.Message);
}
}