diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs index 49ce530633c3ed..afd81e1bb00c7a 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationWriter.cs @@ -291,6 +291,11 @@ private void WriteText(object o, TextAccessor text) } else { + if (TryWritePrimitiveValue(primitiveMapping.TypeDesc!, o)) + { + return; + } + if (!WritePrimitiveValue(primitiveMapping.TypeDesc!, o, out stringValue)) { Debug.Assert(o is byte[]); @@ -808,6 +813,10 @@ private void WriteMember(object? memberValue, AttributeAccessor attribute, TypeD } else { + if (TryWritePrimitiveValue(arrayElementTypeDesc!, ai)) + { + continue; + } if (!WritePrimitiveValue(arrayElementTypeDesc!, ai, out stringValue)) { Debug.Assert(ai is byte[]); @@ -983,6 +992,10 @@ private void WritePrimitive(WritePrimitiveMethodRequirement method, string name, } else { + if (TryWritePrimitiveValue(method, name, ns, typeDesc, o, xmlQualifiedName)) + { + return; + } hasValidStringValue = WritePrimitiveValue(typeDesc, o, out stringValue); } @@ -1191,6 +1204,109 @@ private static string ConvertPrimitiveToString(object o, TypeDesc typeDesc) return stringValue; } + private static bool TryFormatPrimitiveValue(string? formatterName, bool hasCustomFormatter, object? o, Span destination, out int charsWritten) + { + charsWritten = 0; + if (o == null || formatterName == "String") + { + return false; + } + + if (!hasCustomFormatter) + { + switch (formatterName) + { + case "Boolean": + return XmlConvert.TryFormat((bool)o, destination, out charsWritten); + case "Int32": + return XmlConvert.TryFormat((int)o, destination, out charsWritten); + case "Int16": + return XmlConvert.TryFormat((short)o, destination, out charsWritten); + case "Int64": + return XmlConvert.TryFormat((long)o, destination, out charsWritten); + case "Single": + return XmlConvert.TryFormat((float)o, destination, out charsWritten); + case "Double": + return XmlConvert.TryFormat((double)o, destination, out charsWritten); + case "Decimal": + return XmlConvert.TryFormat((decimal)o, destination, out charsWritten); + case "Byte": + return XmlConvert.TryFormat((byte)o, destination, out charsWritten); + case "SByte": + return XmlConvert.TryFormat((sbyte)o, destination, out charsWritten); + case "UInt16": + return XmlConvert.TryFormat((ushort)o, destination, out charsWritten); + case "UInt32": + return XmlConvert.TryFormat((uint)o, destination, out charsWritten); + case "UInt64": + return XmlConvert.TryFormat((ulong)o, destination, out charsWritten); + // Types without direct mapping (ambiguous) + case "Guid": + return XmlConvert.TryFormat((Guid)o, destination, out charsWritten); + case "Char": + return TryFormatChar((char)o, destination, out charsWritten); + case "TimeSpan": + return XmlConvert.TryFormat((TimeSpan)o, destination, out charsWritten); + case "DateTimeOffset": + return XmlConvert.TryFormat((DateTimeOffset)o, destination, out charsWritten); + default: + return false; + } + } + + switch (o) + { + case DateTime dt when formatterName == "DateTime": + return TryFormatDateTime(dt, destination, out charsWritten); + case DateTime d when formatterName == "Date": + return TryFormatDate(d, destination, out charsWritten); + case DateTime t when formatterName == "Time": + return TryFormatTime(t, destination, out charsWritten); + case DateTime: + throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, "Invalid DateTime")); + case char c when formatterName == "Char": + return TryFormatChar(c, destination, out charsWritten); + default: + return false; + } + } + + private bool TryWritePrimitiveValue(WritePrimitiveMethodRequirement method, string name, string? ns, TypeDesc typeDesc, object? o, XmlQualifiedName? xmlQualifiedName) + { + if (typeDesc == ReflectionXmlSerializationReader.StringTypeDesc + || hasRequirement(method, WritePrimitiveMethodRequirement.WriteNullableStringLiteral)) return false; + + bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, primitivesBuffer, out int charsWritten); + if (result) + { + if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteElementString)) + { + WriteElementStringRaw(name, ns, primitivesBuffer, 0, charsWritten, xmlQualifiedName); + } + else if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteAttribute)) + { + WriteAttribute(name, ns, primitivesBuffer, 0, charsWritten); + } + else + { + Debug.Fail("https://github.com/dotnet/runtime/issues/18037: Add More Tests for Serialization Code"); + } + } + + return result; + } + + private bool TryWritePrimitiveValue(TypeDesc typeDesc, object? o) + { + if (typeDesc == ReflectionXmlSerializationReader.StringTypeDesc) return false; + + bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, primitivesBuffer, out int charsWritten); + if (result) + WriteValue(primitivesBuffer, 0, charsWritten); + + return result; + } + [RequiresUnreferencedCode("calls WritePotentiallyReferencingElement")] private void GenerateMembersElement(object o, XmlMembersMapping xmlMembersMapping) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 98ec06f03479a0..0c9eb71c919e51 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -39,7 +39,7 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode private bool _escapeName = true; //char buffer for serializing primitive values - private readonly char[] _primitivesBuffer = new char[64]; + protected readonly char[] primitivesBuffer = new char[64]; // this method must be called before any generated serialization methods are called internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase) @@ -133,16 +133,31 @@ protected static string FromDate(DateTime value) return XmlCustomFormatter.FromDate(value); } + internal static bool TryFormatDate(DateTime value, Span destination, out int charsWritten) + { + return XmlCustomFormatter.TryFormatDate(value, destination, out charsWritten); + } + protected static string FromTime(DateTime value) { return XmlCustomFormatter.FromTime(value); } + internal static bool TryFormatTime(DateTime value, Span destination, out int charsWritten) + { + return XmlCustomFormatter.TryFormatTime(value, destination, out charsWritten); + } + protected static string FromChar(char value) { return XmlCustomFormatter.FromChar(value); } + internal static bool TryFormatChar(char value, Span destination, out int charsWritten) + { + return XmlCustomFormatter.TryFormatChar(value, destination, out charsWritten); + } + protected static string FromEnum(long value, string[] values, long[] ids) { return XmlCustomFormatter.FromEnum(value, values, ids, null); @@ -272,60 +287,60 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT writeRaw = false; break; case TypeCode.Int32: - tryFormatResult = XmlConvert.TryFormat((int)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((int)o, primitivesBuffer, out charsWritten); type = "int"; break; case TypeCode.Boolean: - tryFormatResult = XmlConvert.TryFormat((bool)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((bool)o, primitivesBuffer, out charsWritten); type = "boolean"; break; case TypeCode.Int16: - tryFormatResult = XmlConvert.TryFormat((short)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((short)o, primitivesBuffer, out charsWritten); type = "short"; break; case TypeCode.Int64: - tryFormatResult = XmlConvert.TryFormat((long)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((long)o, primitivesBuffer, out charsWritten); type = "long"; break; case TypeCode.Single: - tryFormatResult = XmlConvert.TryFormat((float)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((float)o, primitivesBuffer, out charsWritten); type = "float"; break; case TypeCode.Double: - tryFormatResult = XmlConvert.TryFormat((double)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((double)o, primitivesBuffer, out charsWritten); type = "double"; break; case TypeCode.Decimal: - tryFormatResult = XmlConvert.TryFormat((decimal)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((decimal)o, primitivesBuffer, out charsWritten); type = "decimal"; break; case TypeCode.DateTime: - tryFormatResult = TryFormatDateTime((DateTime)o, _primitivesBuffer, out charsWritten); + tryFormatResult = TryFormatDateTime((DateTime)o, primitivesBuffer, out charsWritten); type = "dateTime"; break; case TypeCode.Char: - tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, primitivesBuffer, out charsWritten); type = "char"; typeNs = UrtTypes.Namespace; break; case TypeCode.Byte: - tryFormatResult = XmlConvert.TryFormat((byte)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((byte)o, primitivesBuffer, out charsWritten); type = "unsignedByte"; break; case TypeCode.SByte: - tryFormatResult = XmlConvert.TryFormat((sbyte)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((sbyte)o, primitivesBuffer, out charsWritten); type = "byte"; break; case TypeCode.UInt16: - tryFormatResult = XmlConvert.TryFormat((ushort)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ushort)o, primitivesBuffer, out charsWritten); type = "unsignedShort"; break; case TypeCode.UInt32: - tryFormatResult = XmlConvert.TryFormat((uint)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((uint)o, primitivesBuffer, out charsWritten); type = "unsignedInt"; break; case TypeCode.UInt64: - tryFormatResult = XmlConvert.TryFormat((ulong)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((ulong)o, primitivesBuffer, out charsWritten); type = "unsignedLong"; break; @@ -350,19 +365,19 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } else if (t == typeof(Guid)) { - tryFormatResult = XmlConvert.TryFormat((Guid)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((Guid)o, primitivesBuffer, out charsWritten); type = "guid"; typeNs = UrtTypes.Namespace; } else if (t == typeof(TimeSpan)) { - tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, primitivesBuffer, out charsWritten); type = "TimeSpan"; typeNs = UrtTypes.Namespace; } else if (t == typeof(DateTimeOffset)) { - tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, _primitivesBuffer, out charsWritten); + tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, primitivesBuffer, out charsWritten); type = "dateTimeOffset"; typeNs = UrtTypes.Namespace; } @@ -410,11 +425,11 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer."); #if DEBUG const string escapeChars = "<>\"'&"; - ReadOnlySpan span = _primitivesBuffer; + ReadOnlySpan span = primitivesBuffer; Debug.Assert(span.Slice(0, charsWritten).IndexOfAny(escapeChars) == -1, "Primitive value contains illegal xml char."); #endif //all the primitive types except string and XmlQualifiedName writes to the buffer - _w.WriteRaw(_primitivesBuffer, 0, charsWritten); + _w.WriteRaw(primitivesBuffer, 0, charsWritten); } else { @@ -977,34 +992,56 @@ protected void WriteAttribute(string localName, string? ns, string? value) } } - protected void WriteAttribute(string localName, string ns, byte[]? value) + private bool TryWriteStartAttribute(string localName, string? ns) { - if (value == null) return; - - if (localName != "xmlns" && !localName.StartsWith("xmlns:", StringComparison.Ordinal)) + if (localName == "xmlns" || localName.StartsWith("xmlns:", StringComparison.Ordinal)) { - int colon = localName.IndexOf(':'); + return false; + } - if (colon < 0) + int colon = localName.IndexOf(':'); + + if (colon < 0) + { + if (ns == XmlReservedNs.NsXml) { - if (ns == XmlReservedNs.NsXml) - { - _w.WriteStartAttribute("xml", localName, ns); - } - else - { - _w.WriteStartAttribute(null, localName, ns); - } + _w.WriteStartAttribute("xml", localName, ns); } else { - string? prefix = _w.LookupPrefix(ns); - _w.WriteStartAttribute(prefix, localName.Substring(colon + 1), ns); + _w.WriteStartAttribute(null, localName, ns); } + } + else + { + string? prefix = ns != null? _w.LookupPrefix(ns) : null; + _w.WriteStartAttribute(prefix, localName.Substring(colon + 1), ns); + } + + return true; + } + + protected void WriteAttribute(string localName, string ns, byte[]? value) + { + if (value == null || + !TryWriteStartAttribute(localName, ns)) + { + return; + } - XmlCustomFormatter.WriteArrayBase64(_w, value, 0, value.Length); - _w.WriteEndAttribute(); + XmlCustomFormatter.WriteArrayBase64(_w, value, 0, value.Length); + _w.WriteEndAttribute(); + } + + protected void WriteAttribute(string localName, string? ns, char[] value, int index, int count) + { + if (!TryWriteStartAttribute(localName, ns)) + { + return; } + + _w.WriteRaw(value, index, count); + _w.WriteEndAttribute(); } protected void WriteAttribute(string localName, string? value) @@ -1040,6 +1077,11 @@ protected void WriteValue(byte[]? value) XmlCustomFormatter.WriteArrayBase64(_w, value, 0, value.Length); } + protected void WriteValue(char[] value, int index, int count) + { + _w.WriteRaw(value, index, count); + } + protected void WriteStartDocument() { if (_w.WriteState == WriteState.Start) @@ -1127,6 +1169,15 @@ protected void WriteElementStringRaw(string localName, string? ns, byte[]? value _w.WriteEndElement(); } + protected void WriteElementStringRaw(string localName, string? ns, char[] value, int index, int count, XmlQualifiedName? xsiType) + { + _w.WriteStartElement(localName, ns); + if (xsiType != null) + WriteXsiType(xsiType.Name, xsiType.Namespace); + _w.WriteRaw(value, index, count); + _w.WriteEndElement(); + } + protected void WriteRpcResult(string name, string? ns) { if (!_soap12) return; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs index 77a2bb1863992e..f242d780bbfab3 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Xmlcustomformatter.cs @@ -80,6 +80,11 @@ internal static string FromDate(DateTime value) return XmlConvert.ToString(value, "yyyy-MM-dd"); } + internal static bool TryFormatDate(DateTime value, Span destination, out int charsWritten) + { + return XmlConvert.TryFormat(value, "yyyy-MM-dd", destination, out charsWritten); + } + internal static string FromTime(DateTime value) { if (!LocalAppContextSwitches.IgnoreKindInUtcTimeSerialization && value.Kind == DateTimeKind.Utc) @@ -92,6 +97,16 @@ internal static string FromTime(DateTime value) } } + internal static bool TryFormatTime(DateTime value, Span destination, out int charsWritten) + { + if (!LocalAppContextSwitches.IgnoreKindInUtcTimeSerialization && value.Kind == DateTimeKind.Utc) + { + return XmlConvert.TryFormat(DateTime.MinValue + value.TimeOfDay, "HH:mm:ss.fffffffZ", destination, out charsWritten); + } + + return XmlConvert.TryFormat(DateTime.MinValue + value.TimeOfDay, "HH:mm:ss.fffffffzzzzzz", destination, out charsWritten); + } + internal static string FromDateTime(DateTime value) { if (Mode == DateTimeSerializationSection.DateTimeSerializationMode.Local) @@ -121,6 +136,11 @@ internal static string FromChar(char value) return XmlConvert.ToString((ushort)value); } + internal static bool TryFormatChar(char value, Span destination, out int charsWritten) + { + return XmlConvert.TryFormat((ushort)value, destination, out charsWritten); + } + [return: NotNullIfNotNull(nameof(name))] internal static string? FromXmlName(string? name) { diff --git a/src/libraries/System.Xml.XmlSerializer/ref/System.Xml.XmlSerializer.cs b/src/libraries/System.Xml.XmlSerializer/ref/System.Xml.XmlSerializer.cs index f53a671de838c8..99869ea01854e8 100644 --- a/src/libraries/System.Xml.XmlSerializer/ref/System.Xml.XmlSerializer.cs +++ b/src/libraries/System.Xml.XmlSerializer/ref/System.Xml.XmlSerializer.cs @@ -585,6 +585,7 @@ public Fixup(object? o, System.Xml.Serialization.XmlSerializationFixupCallback c public abstract partial class XmlSerializationWriter : System.Xml.Serialization.XmlSerializationGeneratedCode { protected XmlSerializationWriter() { } + protected readonly char[] primitivesBuffer; protected bool EscapeName { get { throw null; } set { } } protected System.Collections.ArrayList? Namespaces { get { throw null; } set { } } protected System.Xml.XmlWriter Writer { get { throw null; } set { } } @@ -627,6 +628,7 @@ protected void WriteAttribute(string localName, byte[]? value) { } protected void WriteAttribute(string localName, string? value) { } protected void WriteAttribute(string localName, string ns, byte[]? value) { } protected void WriteAttribute(string localName, string? ns, string? value) { } + protected void WriteAttribute(string localName, string? ns, char[] value, int index, int count) { } protected void WriteAttribute(string? prefix, string localName, string? ns, string? value) { } protected void WriteElementEncoded(System.Xml.XmlNode? node, string name, string? ns, bool isNullable, bool any) { } protected void WriteElementLiteral(System.Xml.XmlNode? node, string name, string? ns, bool isNullable, bool any) { } @@ -646,6 +648,7 @@ protected void WriteElementStringRaw(string localName, string? ns, byte[]? value protected void WriteElementStringRaw(string localName, string? ns, string? value) { } protected void WriteElementStringRaw(string localName, string? ns, string? value, System.Xml.XmlQualifiedName? xsiType) { } protected void WriteElementStringRaw(string localName, string? value, System.Xml.XmlQualifiedName? xsiType) { } + protected void WriteElementStringRaw(string localName, string? ns, char[] value, int index, int count, System.Xml.XmlQualifiedName? xsiType) { } protected void WriteEmptyTag(string? name) { } protected void WriteEmptyTag(string? name, string? ns) { } protected void WriteEndElement() { } @@ -690,6 +693,7 @@ protected void WriteStartElement(string name, string? ns, object? o, bool writeP protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiType) { } protected void WriteValue(byte[]? value) { } protected void WriteValue(string? value) { } + protected void WriteValue(char[] value, int index, int count) { } protected void WriteXmlAttribute(System.Xml.XmlNode node) { } protected void WriteXmlAttribute(System.Xml.XmlNode node, object? container) { } protected void WriteXsiType(string name, string? ns) { }