diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs index f82937036093f0..4f774a4f98a31e 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs @@ -132,6 +132,8 @@ private enum XsdDateTimeKind private static ReadOnlySpan DaysToMonth366 => new int[] { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; + private const int CharStackBufferSize = 64; + /// /// Constructs an XsdDateTime from a string using specific format. /// @@ -495,7 +497,17 @@ public static implicit operator DateTimeOffset(XsdDateTime xdt) /// public override string ToString() { - var vsb = new ValueStringBuilder(stackalloc char[64]); + Span destination = stackalloc char[CharStackBufferSize]; + bool success = TryFormat(destination, out int charsWritten); + Debug.Assert(success); + + return destination.Slice(0, charsWritten).ToString(); + } + + public bool TryFormat(Span destination, out int charsWritten) + { + var vsb = new ValueStringBuilder(destination); + switch (InternalTypeCode) { case DateTimeTypeCode.DateTime: @@ -534,7 +546,9 @@ public override string ToString() break; } PrintZone(ref vsb); - return vsb.ToString(); + + charsWritten = vsb.Length; + return destination.Length >= vsb.Length; } // Serialize year, month and day diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs index 817e01734194e1..f4612b2c541f40 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs @@ -23,6 +23,7 @@ internal struct XsdDuration private uint _nanoseconds; // High bit is used to indicate whether duration is negative private const uint NegativeBit = 0x80000000; + private const int CharStackBufferSize = 32; private enum Parts { @@ -341,7 +342,16 @@ public override string ToString() /// internal string ToString(DurationType durationType) { - var vsb = new ValueStringBuilder(stackalloc char[20]); + Span destination = stackalloc char[CharStackBufferSize]; + bool success = TryFormat(destination, out int charsWritten, durationType); + Debug.Assert(success); + + return destination.Slice(0, charsWritten).ToString(); + } + + public bool TryFormat(Span destination, out int charsWritten, DurationType durationType = DurationType.Duration) + { + var vsb = new ValueStringBuilder(destination); int nanoseconds, digit, zeroIdx, len; if (IsNegative) @@ -411,7 +421,9 @@ internal string ToString(DurationType durationType) } vsb.EnsureCapacity(zeroIdx + 1); - vsb.Append(tmpSpan.Slice(0, zeroIdx - len + 1)); + int nanoSpanLength = zeroIdx - len + 1; + bool successCopy = tmpSpan[..nanoSpanLength].TryCopyTo(vsb.AppendSpan(nanoSpanLength)); + Debug.Assert(successCopy); } vsb.Append('S'); } @@ -428,7 +440,8 @@ internal string ToString(DurationType durationType) vsb.Append("0M"); } - return vsb.ToString(); + charsWritten = vsb.Length; + return destination.Length >= vsb.Length; } internal static Exception? TryParse(string s, out XsdDuration result) 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..1449ccb8bef2ff 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,131 @@ 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 XmlConvert.TryFormat((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; + + char[] buffer = RentPrimitivesBuffer(); + bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, buffer, out int charsWritten); + if (result) + { + if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteElementString)) + { + WriteElementRaw(name, ns, buffer, 0, charsWritten, xmlQualifiedName); + } + else if (hasRequirement(method, WritePrimitiveMethodRequirement.WriteAttribute)) + { + WriteAttribute(name, ns, buffer, 0, charsWritten); + } + else + { + Debug.Fail("https://github.com/dotnet/runtime/issues/18037: Add More Tests for Serialization Code"); + } + } + + ReturnPrimitivesBuffer(buffer); + return result; + } + + private bool TryWritePrimitiveValue(TypeDesc typeDesc, object? o) + { + if (typeDesc == ReflectionXmlSerializationReader.StringTypeDesc) return false; + + char[] buffer = RentPrimitivesBuffer(); + bool result = TryFormatPrimitiveValue(typeDesc.FormatterName, typeDesc.HasCustomFormatter, o, buffer, out int charsWritten); + if (result) + WriteValue(buffer, 0, charsWritten); + + ReturnPrimitivesBuffer(buffer); + return result; + } + + private 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); + } + + private static bool TryFormatDate(DateTime value, Span destination, out int charsWritten) + { + return XmlConvert.TryFormat(value, "yyyy-MM-dd", destination, out charsWritten); + } + + private static bool TryFormatChar(char value, Span destination, out int charsWritten) + { + return XmlConvert.TryFormat((ushort)value, destination, out charsWritten); + } + [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 cfa361fa3f2a06..69d5bab20c2778 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 @@ -38,6 +38,10 @@ public abstract class XmlSerializationWriter : XmlSerializationGeneratedCode private bool _soap12; private bool _escapeName = true; + //char buffer for serializing primitive values + private char[]? _primitivesBuffer; + private const int PrimitivesBufferSize = 64; + // this method must be called before any generated serialization methods are called internal void Init(XmlWriter w, XmlSerializerNamespaces? namespaces, string? encodingStyle, string? idBase) { @@ -120,6 +124,11 @@ protected static string FromDateTime(DateTime value) return XmlCustomFormatter.FromDateTime(value); } + internal static bool TryFormatDateTime(DateTime value, Span destination, out int charsWritten) + { + return XmlCustomFormatter.TryFormatDateTime(value, destination, out charsWritten); + } + protected static string FromDate(DateTime value) { return XmlCustomFormatter.FromDate(value); @@ -243,17 +252,24 @@ private XmlQualifiedName GetPrimitiveTypeName(Type type) return new XmlQualifiedName(typeName, typeNs); } + protected char[] RentPrimitivesBuffer() => Interlocked.Exchange(ref _primitivesBuffer, null) ?? new char[PrimitivesBufferSize]; + + protected void ReturnPrimitivesBuffer(char[] buffer) => Interlocked.Exchange(ref _primitivesBuffer, buffer); + [RequiresUnreferencedCode(XmlSerializer.TrimSerializationWarning)] protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiType) { - string? value; + string? value = null; string type; string typeNs = XmlSchema.Namespace; bool writeRaw = true; bool writeDirect = false; Type t = o.GetType(); bool wroteStartElement = false; + bool? tryFormatResult = null; + int charsWritten = -1; + char[] buffer = RentPrimitivesBuffer(); switch (Type.GetTypeCode(t)) { case TypeCode.String: @@ -262,60 +278,60 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT writeRaw = false; break; case TypeCode.Int32: - value = XmlConvert.ToString((int)o); + tryFormatResult = XmlConvert.TryFormat((int)o, buffer, out charsWritten); type = "int"; break; case TypeCode.Boolean: - value = XmlConvert.ToString((bool)o); + tryFormatResult = XmlConvert.TryFormat((bool)o, buffer, out charsWritten); type = "boolean"; break; case TypeCode.Int16: - value = XmlConvert.ToString((short)o); + tryFormatResult = XmlConvert.TryFormat((short)o, buffer, out charsWritten); type = "short"; break; case TypeCode.Int64: - value = XmlConvert.ToString((long)o); + tryFormatResult = XmlConvert.TryFormat((long)o, buffer, out charsWritten); type = "long"; break; case TypeCode.Single: - value = XmlConvert.ToString((float)o); + tryFormatResult = XmlConvert.TryFormat((float)o, buffer, out charsWritten); type = "float"; break; case TypeCode.Double: - value = XmlConvert.ToString((double)o); + tryFormatResult = XmlConvert.TryFormat((double)o, buffer, out charsWritten); type = "double"; break; case TypeCode.Decimal: - value = XmlConvert.ToString((decimal)o); + tryFormatResult = XmlConvert.TryFormat((decimal)o, buffer, out charsWritten); type = "decimal"; break; case TypeCode.DateTime: - value = FromDateTime((DateTime)o); + tryFormatResult = TryFormatDateTime((DateTime)o, buffer, out charsWritten); type = "dateTime"; break; case TypeCode.Char: - value = FromChar((char)o); + tryFormatResult = XmlConvert.TryFormat((ushort)(char)o, buffer, out charsWritten); type = "char"; typeNs = UrtTypes.Namespace; break; case TypeCode.Byte: - value = XmlConvert.ToString((byte)o); + tryFormatResult = XmlConvert.TryFormat((byte)o, buffer, out charsWritten); type = "unsignedByte"; break; case TypeCode.SByte: - value = XmlConvert.ToString((sbyte)o); + tryFormatResult = XmlConvert.TryFormat((sbyte)o, buffer, out charsWritten); type = "byte"; break; case TypeCode.UInt16: - value = XmlConvert.ToString((ushort)o); + tryFormatResult = XmlConvert.TryFormat((ushort)o, buffer, out charsWritten); type = "unsignedShort"; break; case TypeCode.UInt32: - value = XmlConvert.ToString((uint)o); + tryFormatResult = XmlConvert.TryFormat((uint)o, buffer, out charsWritten); type = "unsignedInt"; break; case TypeCode.UInt64: - value = XmlConvert.ToString((ulong)o); + tryFormatResult = XmlConvert.TryFormat((ulong)o, buffer, out charsWritten); type = "unsignedLong"; break; @@ -340,19 +356,19 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT } else if (t == typeof(Guid)) { - value = XmlConvert.ToString((Guid)o); + tryFormatResult = XmlConvert.TryFormat((Guid)o, buffer, out charsWritten); type = "guid"; typeNs = UrtTypes.Namespace; } else if (t == typeof(TimeSpan)) { - value = XmlConvert.ToString((TimeSpan)o); + tryFormatResult = XmlConvert.TryFormat((TimeSpan)o, buffer, out charsWritten); type = "TimeSpan"; typeNs = UrtTypes.Namespace; } else if (t == typeof(DateTimeOffset)) { - value = XmlConvert.ToString((DateTimeOffset)o); + tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)o, buffer, out charsWritten); type = "dateTimeOffset"; typeNs = UrtTypes.Namespace; } @@ -371,10 +387,15 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT xmlNodes[i].WriteTo(_w); } _w.WriteEndElement(); + ReturnPrimitivesBuffer(buffer); return; } else + { + ReturnPrimitivesBuffer(buffer); throw CreateUnknownTypeException(t); + } + break; } if (!wroteStartElement) @@ -387,22 +408,36 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT if (xsiType) WriteXsiType(type, typeNs); - if (value == null) - { - _w.WriteAttributeString("nil", XmlSchema.InstanceNamespace, "true"); - } - else if (writeDirect) + if (writeDirect) { // only one type currently writes directly to XML stream XmlCustomFormatter.WriteArrayBase64(_w, (byte[])o, 0, ((byte[])o).Length); } - else if (writeRaw) + else if (tryFormatResult != null) { - _w.WriteRaw(value); + Debug.Assert(tryFormatResult.Value, "Something goes wrong with formatting primitives to the buffer."); +#if DEBUG + const string escapeChars = "<>\"'&"; + ReadOnlySpan span = buffer; + 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(buffer, 0, charsWritten); } else - _w.WriteString(value); + { + if (value == null) + _w.WriteAttributeString("nil", XmlSchema.InstanceNamespace, "true"); + else if (writeRaw) + { + _w.WriteRaw(value); + } + else + _w.WriteString(value); + } + _w.WriteEndElement(); + ReturnPrimitivesBuffer(buffer); } private string GetQualifiedName(string name, string? ns) @@ -981,6 +1016,40 @@ protected void WriteAttribute(string localName, string ns, byte[]? value) } } + protected void WriteAttribute(string localName, string? ns, char[] value, int index, int count) + { + if (localName != "xmlns" && !localName.StartsWith("xmlns:", StringComparison.Ordinal)) + { + int colon = localName.IndexOf(':'); + + if (colon < 0) + { + if (ns == XmlReservedNs.NsXml) + { + string? prefix = _w.LookupPrefix(ns); + + if (prefix == null || prefix.Length == 0) + { + prefix = "xml"; + } + _w.WriteStartAttribute(prefix, localName, ns); + } + else + { + _w.WriteStartAttribute(null, localName, ns); + } + } + else + { + string prefix = localName.Substring(0, colon); + _w.WriteStartAttribute(prefix, localName.Substring(colon + 1), ns); + } + } + + _w.WriteRaw(value, index, count); + _w.WriteEndAttribute(); + } + protected void WriteAttribute(string localName, string? value) { if (value == null) return; @@ -1014,6 +1083,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) @@ -1432,6 +1506,15 @@ protected void WriteNamespaceDeclarations(XmlSerializerNamespaces? xmlns) _namespaces = null; } + protected void WriteElementRaw(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(); + } + private string NextPrefix() { if (_usedPrefixes == null) 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 2338d8d46e949b..77a2bb1863992e 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 @@ -105,6 +105,17 @@ internal static string FromDateTime(DateTime value) } } + internal static bool TryFormatDateTime(DateTime value, Span destination, out int charsWritten) + { + if (Mode == DateTimeSerializationSection.DateTimeSerializationMode.Local) + { + return XmlConvert.TryFormat(value, "yyyy-MM-ddTHH:mm:ss.fffffffzzzzzz", destination, out charsWritten); + } + + // for mode DateTimeSerializationMode.Roundtrip and DateTimeSerializationMode.Default + return XmlConvert.TryFormat(value, XmlDateTimeSerializationMode.RoundtripKind, destination, out charsWritten); + } + internal static string FromChar(char value) { return XmlConvert.ToString((ushort)value); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index c465eb96094f0b..07b6bb93691a7a 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -1645,5 +1645,173 @@ internal static Exception CreateInvalidNameCharException(string name, int index, { return CreateException(index == 0 ? SR.Xml_BadStartNameChar : SR.Xml_BadNameChar, XmlException.BuildCharExceptionArgs(name, index), exceptionType, 0, index + 1); } + + internal static bool TryFormat(bool value, Span destination, out int charsWritten) + { + string valueAsString = value ? "true" : "false"; + + charsWritten = valueAsString.Length; + return valueAsString.TryCopyTo(destination); + } + + internal static bool TryFormat(char value, Span destination, out int charsWritten) + { + charsWritten = 1; + if (destination.Length < 1) return false; + + destination[0] = value; + return true; + } + + internal static bool TryFormat(decimal value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, NumberFormatInfo.InvariantInfo); + } + + internal static bool TryFormat(sbyte value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(short value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(int value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(long value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(byte value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(ushort value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(uint value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(ulong value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, default, CultureInfo.InvariantCulture); + } + + internal static bool TryFormat(float value, Span destination, out int charsWritten) + { + ReadOnlySpan valueSpan; + + if (!float.IsFinite(value)) + { + if (float.IsNaN(value)) + valueSpan = "NaN"; + else + valueSpan = float.IsNegative(value) ? "-INF" : "INF"; + } + else if (IsNegativeZero((double)value)) + { + valueSpan = "-0"; + } + else + { + return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + } + + charsWritten = valueSpan.Length; + return valueSpan.TryCopyTo(destination); + } + + internal static bool TryFormat(double value, Span destination, out int charsWritten) + { + ReadOnlySpan valueSpan; + + if (!double.IsFinite(value)) + { + if (double.IsNaN(value)) + valueSpan = "NaN"; + else + valueSpan = double.IsNegative(value) ? "-INF" : "INF"; + } + else if (IsNegativeZero(value)) + { + valueSpan = "-0"; + } + else + { + return value.TryFormat(destination, out charsWritten, "R", NumberFormatInfo.InvariantInfo); + } + + charsWritten = valueSpan.Length; + return valueSpan.TryCopyTo(destination); + } + + internal static bool TryFormat(TimeSpan value, Span destination, out int charsWritten) + { + return new XsdDuration(value).TryFormat(destination, out charsWritten); + } + + internal static bool TryFormat(DateTime value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, format, DateTimeFormatInfo.InvariantInfo); + } + + internal static bool TryFormat(DateTime value, Span destination, out int charsWritten) + { + return TryFormat(value, XmlDateTimeSerializationMode.RoundtripKind, destination, out charsWritten); + } + + internal static bool TryFormat(DateTime value, XmlDateTimeSerializationMode dateTimeOption, Span destination, out int charsWritten) + { + switch (dateTimeOption) + { + case XmlDateTimeSerializationMode.Local: + value = SwitchToLocalTime(value); + break; + + case XmlDateTimeSerializationMode.Utc: + value = SwitchToUtcTime(value); + break; + + case XmlDateTimeSerializationMode.Unspecified: + value = new DateTime(value.Ticks, DateTimeKind.Unspecified); + break; + + case XmlDateTimeSerializationMode.RoundtripKind: + break; + + default: + throw new ArgumentException(SR.Format(SR.Sch_InvalidDateTimeOption, dateTimeOption, nameof(dateTimeOption))); + } + + XsdDateTime xsdDateTime = new XsdDateTime(value, XsdDateTimeFlags.DateTime); + return xsdDateTime.TryFormat(destination, out charsWritten); + } + + internal static bool TryFormat(DateTimeOffset value, Span destination, out int charsWritten) + { + XsdDateTime xsdDateTime = new XsdDateTime(value); + return xsdDateTime.TryFormat(destination, out charsWritten); + } + + internal static bool TryFormat(DateTimeOffset value, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten, format, DateTimeFormatInfo.InvariantInfo); + } + + internal static bool TryFormat(Guid value, Span destination, out int charsWritten) + { + return value.TryFormat(destination, out charsWritten); + } } } diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs index 29a7880968a5f4..9fa615c142d72c 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.RuntimeOnly.cs @@ -411,6 +411,51 @@ public static void Xml_CollectionRoot() Assert.True((int)y[1] == 45); } + [Fact] + public static void Xml_CollectionRoot_MorePrimitiveTypes() + { + DateTime now = new DateTime(2022, 9, 30, 9, 4, 15, DateTimeKind.Utc); + DateTimeOffset dtoNow = now.AddDays(1); + TimeSpan ts = new TimeSpan(1, 2, 3, 4, 5); + MyCollection x = new MyCollection(123.45m, now, ts, dtoNow, (short)55, 2345324L, (sbyte)11, (ushort)34, (uint)4564, (ulong)456734767, + new byte[] { 33, 44, 55 }, (byte)67); + MyCollection y = SerializeAndDeserialize(x, +@" + + 123.45 + 2022-09-30T09:04:15Z + P1DT2H3M4.005S + 2022-10-01T09:04:15Z + 55 + 2345324 + 11 + 34 + 4564 + 456734767 + ISw3 + 67 +"); + + Assert.NotNull(y); + Assert.True(y.Count == 12); + Assert.True((decimal)y[0] == 123.45m); + Assert.True((DateTime)y[1] == now); + Assert.True((TimeSpan)y[2] == ts); + Assert.True((DateTimeOffset)y[3] == dtoNow); + Assert.True((short)y[4] == 55); + Assert.True((long)y[5] == 2345324L); + Assert.True((sbyte)y[6] == 11); + Assert.True((ushort)y[7] == 34); + Assert.True((uint)y[8] == 4564); + Assert.True((ulong)y[9] == 456734767); + Assert.True(y[10] is byte[]); + Assert.Equal(3, ((byte[])y[10]).Length); + Assert.Equal(33, ((byte[])y[10])[0]); + Assert.Equal(44, ((byte[])y[10])[1]); + Assert.Equal(55, ((byte[])y[10])[2]); + Assert.True((byte)y[11] == 67); + } + [Fact] public static void Xml_EnumerableRoot() { 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..147977aef4f879 100644 --- a/src/libraries/System.Xml.XmlSerializer/ref/System.Xml.XmlSerializer.cs +++ b/src/libraries/System.Xml.XmlSerializer/ref/System.Xml.XmlSerializer.cs @@ -627,6 +627,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) { } @@ -690,9 +691,13 @@ 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) { } + protected void WriteElementRaw(string localName, string? ns, char[] value, int index, int count, System.Xml.XmlQualifiedName? xsiType) { } + protected char[] RentPrimitivesBuffer() { throw null; } + protected void ReturnPrimitivesBuffer(char[] buffer) { } } public partial class XmlSerializer {