From 981ec07ce11c14a31e451f733ec0aa67aa3ed5f4 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:17:31 +0100 Subject: [PATCH] Add XML helper --- src/CuteUtils.Tests/Misc/XmlHelperTests.cs | 69 ++++++++++++++++ src/CuteUtils/Misc/XmlHelper.cs | 94 ++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/CuteUtils.Tests/Misc/XmlHelperTests.cs create mode 100644 src/CuteUtils/Misc/XmlHelper.cs diff --git a/src/CuteUtils.Tests/Misc/XmlHelperTests.cs b/src/CuteUtils.Tests/Misc/XmlHelperTests.cs new file mode 100644 index 0000000..0b018eb --- /dev/null +++ b/src/CuteUtils.Tests/Misc/XmlHelperTests.cs @@ -0,0 +1,69 @@ +using CuteUtils.Misc; + +namespace CuteUtils.Tests.Misc; + +[TestClass] +public class XmlHelperTests +{ + public class TestModel + { + public string? Name { get; set; } + public int Age { get; set; } + public NestedModel? Child { get; set; } + } + + public class NestedModel + { + public string? Value { get; set; } + } + + [TestMethod] + public void SerializeDeserialize_RoundTrip_RetainsData() + { + TestModel model = new TestModel + { + Name = "Alice", + Age = 30, + Child = new NestedModel { Value = "Inner" } + }; + + string xml = XmlHelper.Serialize(model); + Assert.IsFalse(string.IsNullOrWhiteSpace(xml), "Serialized XML should not be empty."); + Assert.Contains("Alice", xml, "XML should contain the Name value."); + Assert.Contains("30", xml, "XML should contain the Age value."); + Assert.Contains("Inner", xml, "XML should contain the Nested Value."); + + TestModel? deserialized = XmlHelper.Deserialize(xml); + Assert.IsNotNull(deserialized, "Deserialized object should not be null."); + Assert.AreEqual(model.Name, deserialized!.Name); + Assert.AreEqual(model.Age, deserialized.Age); + Assert.AreEqual(model.Child?.Value, deserialized.Child?.Value); + } + + [TestMethod] + public void Serialize_OmitsNamespaceDeclarations() + { + TestModel model = new TestModel { Name = "Bob", Age = 25 }; + string xml = XmlHelper.Serialize(model); + + // Expect no namespace attributes like xmlns or common schema prefixes + Assert.DoesNotContain("xmlns", xml, "XML should not contain namespace declarations."); + Assert.DoesNotContain("xsi:", xml, "XML should not contain xsi prefix."); + Assert.DoesNotContain("xsd:", xml, "XML should not contain xsd prefix."); + } + + [TestMethod] + public void Deserialize_NullXml_ThrowsArgumentNullException() + { + _ = Assert.ThrowsExactly(() => XmlHelper.Deserialize(null!)); + } + + [TestMethod] + public void GetXmlSchema_ReturnsSchemaContainingTypeName() + { + string schema = XmlHelper.GetXmlSchema(); + Assert.IsFalse(string.IsNullOrWhiteSpace(schema), "Schema should not be empty."); + Assert.IsGreaterThanOrEqualTo(0, schema.IndexOf("schema", StringComparison.OrdinalIgnoreCase), "Schema should contain 'schema' element."); + Assert.Contains("TestModel", schema, "Schema should reference the TestModel type."); + } +} \ No newline at end of file diff --git a/src/CuteUtils/Misc/XmlHelper.cs b/src/CuteUtils/Misc/XmlHelper.cs new file mode 100644 index 0000000..56fe364 --- /dev/null +++ b/src/CuteUtils/Misc/XmlHelper.cs @@ -0,0 +1,94 @@ +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace CuteUtils.Misc; + +/// +/// Provides simple XML serialization and deserialization helpers and schema generation helpers. +/// +public static class XmlHelper +{ + /// + /// Deserializes an XML string into an instance of . + /// + /// The target reference type to deserialize into. Must be a class. + /// The XML string to deserialize. Must not be null. + /// Optional to control reader behavior. If null, default settings are used. + /// An instance of if deserialization succeeds; otherwise, null. + /// Thrown when is null. + /// May be thrown by when deserialization fails. + public static T? Deserialize(string xml, XmlReaderSettings? settings = null) where T : class + { + ArgumentNullException.ThrowIfNull(xml); + + XmlSerializer serializer = new XmlSerializer(typeof(T)); + + using StringReader stringReader = new StringReader(xml); + using XmlReader xmlReader = XmlReader.Create(stringReader, settings); + + return serializer.Deserialize(xmlReader) as T; + } + + /// + /// Serializes an object to its XML representation. + /// + /// The type of the object to serialize. + /// The object instance to serialize. Must not be null. + /// Optional to control writer behavior. If null, default settings are used. + /// Optional to control namespace declarations. If null, an empty namespace is used to omit declarations. + /// A string containing the XML representation of . + /// Thrown when is null. + /// May be thrown by when serialization fails. + public static string Serialize(T obj, XmlWriterSettings? settings = null, XmlSerializerNamespaces? xmlSerializerNamespaces = null) + { + ArgumentNullException.ThrowIfNull(obj); + + settings ??= new XmlWriterSettings(); + + if (xmlSerializerNamespaces == null) + { + xmlSerializerNamespaces = new XmlSerializerNamespaces(); + xmlSerializerNamespaces.Add(string.Empty, string.Empty); + } + + XmlSerializer xmlSerializer = new(typeof(T)); + + // Omit namespace declarations by adding an empty namespace + xmlSerializerNamespaces.Add(string.Empty, string.Empty); + + using StringWriter stringWriter = new StringWriter(); + using XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings); + + xmlSerializer.Serialize(xmlWriter, obj, xmlSerializerNamespaces); + return stringWriter.ToString(); + } + + /// + /// Generates XML schema definitions for the specified type . + /// + /// The type for which to generate XML schema. + /// Optional to control output formatting. If null, default settings are used. + /// A string containing one or more XML schema documents representing . + /// May be thrown when schema export fails. + public static string GetXmlSchema(XmlWriterSettings? settings = null) + { + settings ??= new XmlWriterSettings(); + + XmlSchemas schemas = []; + XmlSchemaExporter exporter = new XmlSchemaExporter(schemas); + XmlTypeMapping mapping = new XmlReflectionImporter().ImportTypeMapping(typeof(T)); + + exporter.ExportTypeMapping(mapping); + + using StringWriter stringWriter = new StringWriter(); + using XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings); + + foreach (XmlSchema schema in schemas) + { + schema.Write(xmlWriter); + } + + return stringWriter.ToString(); + } +} \ No newline at end of file