Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add char[] buffer to XmlRawWriter. by TrayanZapryanov · Pull Request #75411 · dotnet/runtime · GitHub
Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add char[] buffer to XmlRawWriter. by TrayanZapryanov · Pull Request #75411 · dotnet/runtime · GitHub
Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add char[] buffer to XmlRawWriter. by TrayanZapryanov · Pull Request #75411 · dotnet/runtime · GitHub
Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add char[] buffer to XmlRawWriter. by TrayanZapryanov · Pull Request #75411 · dotnet/runtime · GitHub
Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add char[] buffer to XmlRawWriter. by TrayanZapryanov · Pull Request #75411 · dotnet/runtime · GitHub
Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add char[] buffer to XmlRawWriter. by TrayanZapryanov · Pull Request #75411 · dotnet/runtime · GitHub
Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add char[] buffer to XmlRawWriter. by TrayanZapryanov · Pull Request #75411 · dotnet/runtime · GitHub
Skip to content
141 changes: 126 additions & 15 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ internal abstract partial class XmlRawWriter : XmlWriter
// namespace resolver
protected IXmlNamespaceResolver? _resolver;

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

//
// XmlWriter implementation
//
Expand DownExpand Up@@ -143,17 +146,19 @@ public override void WriteCData(string? text)
WriteString(text);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteCharEntity(char ch)
{
WriteString(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
// Forward call to WriteChars.
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
WriteString(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
WriteChars(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -162,16 +167,10 @@ public override void WriteWhitespace(string? ws)
WriteString(ws);
}

// Forward call to WriteString(string).
// Forward call to WriteRaw(char[]).
public override void WriteChars(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override void WriteRaw(char[] buffer, int index, int count)
{
WriteString(new string(buffer, index, count));
WriteRaw(buffer, index, count);
}

// Forward call to WriteString(string).
Expand All@@ -185,7 +184,76 @@ public override void WriteValue(object value)
{
ArgumentNullException.ThrowIfNull(value);

WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
Type sourceType = value.GetType();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be adding a lot of branching where it previously didn't exist. Could this be done differently? I'm not super familiar with this code, but one thought might be to expose a TryFormat method to XmlUntypedConverter.Untyped.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in XmlUntypedConverter.ToString(object) is already similar:
```cs
public override string ToString(object value, IXmlNamespaceResolver? nsResolver)
{
ArgumentNullException.ThrowIfNull(value);

 Type sourceType = value.GetType();
if (sourceType == BooleanType) return XmlConvert.ToString((bool)value);
if (sourceType == ByteType) return XmlConvert.ToString((byte)value);
if (sourceType == ByteArrayType) return Base64BinaryToString((byte[])value);
if (sourceType == DateTimeType) return DateTimeToString((DateTime)value);
if (sourceType == DateTimeOffsetType) return DateTimeOffsetToString((DateTimeOffset)value);
if (sourceType == DecimalType) return XmlConvert.ToString((decimal)value);
if (sourceType == DoubleType) return XmlConvert.ToString((double)value);
if (sourceType == Int16Type) return XmlConvert.ToString((short)value);
if (sourceType == Int32Type) return XmlConvert.ToString((int)value);
if (sourceType == Int64Type) return XmlConvert.ToString((long)value);
if (sourceType == SByteType) return XmlConvert.ToString((sbyte)value);
if (sourceType == SingleType) return XmlConvert.ToString((float)value);
if (sourceType == StringType) return ((string)value);
if (sourceType == TimeSpanType) return DurationToString((TimeSpan)value);
if (sourceType == UInt16Type) return XmlConvert.ToString((ushort)value);
if (sourceType == UInt32Type) return XmlConvert.ToString((uint)value);
if (sourceType == UInt64Type) return XmlConvert.ToString((ulong)value);
if (IsDerivedFrom(sourceType, UriType)) return AnyUriToString((Uri)value);
if (sourceType == XmlAtomicValueType) return ((string)((XmlAtomicValue)value).ValueAs(StringType, nsResolver));
if (IsDerivedFrom(sourceType, XmlQualifiedNameType)) return QNameToString((XmlQualifiedName)value, nsResolver);
return (string)ChangeTypeWildcardDestination(value, StringType, nsResolver);
}

bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limitation is really strange, but we need to adapt XmlValueConverter to allow Guid in it's methods.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
WriteString(XmlUntypedConverter.Untyped.ToString(value, _resolver));
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
Expand All@@ -198,7 +266,8 @@ public override void WriteValue(DateTimeOffset value)
{
// For compatibility with custom writers, XmlWriter writes DateTimeOffset as DateTime.
// Our internal writers should use the DateTimeOffset-String conversion from XmlConvert.
WriteString(XmlConvert.ToString(value));

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems not relevant as even now it is using

publicstaticstringToString(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is connected with base code ?

publicvirtualvoidWriteValue(DateTimeOffsetvalue)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krwq Do you know if somebody is changing this Mode ?

internalstaticclassXmlCustomFormatter
{
privatestaticDateTimeSerializationSection.DateTimeSerializationModes_mode;
privatestaticDateTimeSerializationSection.DateTimeSerializationModeMode
{
get
{
if(s_mode==DateTimeSerializationSection.DateTimeSerializationMode.Default)
{
s_mode=DateTimeSerializationSection.DateTimeSerializationMode.Roundtrip;
}
returns_mode;
}
}

I saw similar code in XmlSerializers and the only way to change is with Reflection and directly manipulating fiedl.

XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Copying to XmlRawWriter is not currently supported.
Expand All@@ -217,6 +286,48 @@ public override void WriteNode(System.Xml.XPath.XPathNavigator navigator, bool d
throw new InvalidOperationException(SR.Xml_InvalidOperation);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

//
// XmlRawWriter methods and properties
//
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,17 +109,19 @@ public override Task WriteCDataAsync(string? text)
return WriteStringAsync(text);
}

// Forward call to WriteString(string).
// Forward call to WriteCharsAsync.
public override Task WriteCharEntityAsync(char ch)
{
return WriteStringAsync(char.ToString(ch));
XmlConvert.TryFormat((ushort)ch, _primitivesBuffer, out int charsWritten);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten);
}

// Forward call to WriteString(string).
public override Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
ReadOnlySpan<char> entity = stackalloc char[] { lowChar, highChar };
return WriteStringAsync(new string(entity));
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer, out int charsWritten);
XmlConvert.TryFormat((ushort)lowChar, _primitivesBuffer.AsSpan(charsWritten), out int charsWritten2);
return WriteCharsAsync(_primitivesBuffer, 0, charsWritten + charsWritten2);
}

// Forward call to WriteString(string).
Expand All@@ -128,18 +130,6 @@ public override Task WriteWhitespaceAsync(string? ws)
return WriteStringAsync(ws);
}

// Forward call to WriteString(string).
public override Task WriteCharsAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(char[] buffer, int index, int count)
{
return WriteStringAsync(new string(buffer, index, count));
}

// Forward call to WriteString(string).
public override Task WriteRawAsync(string data)
{
Expand Down
122 changes: 122 additions & 0 deletions src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,9 @@ private enum Token
private char[] _indentChars;
private static readonly char[] s_defaultIndentChars = CreateDefaultIndentChars();

//char buffer for serializing primitive values
private readonly char[] _primitivesBuffer = new char[128];

private static char[] CreateDefaultIndentChars()
{
var result = new char[IndentArrayLength];
Expand DownExpand Up@@ -981,6 +984,125 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}
}

// Override in order to handle Xml simple typed values and to pass resolver for QName values
public override void WriteValue(object value)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole logic for writing with buffer is duplicated in XmlTextWriter and XmlRawWriter.
One suggestion is to create "public" class( this is needed as XmlTextWriter is public) and encapsulate logic in it.
The other is to have some static helper class which works by ref with char[].

Which one do you prefer ?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication logic reduced, but the question still stays open.

{
ArgumentNullException.ThrowIfNull(value);

Type sourceType = value.GetType();
bool tryFormatResult = false;
int charsWritten = -1;
switch (Type.GetTypeCode(sourceType))
{
case TypeCode.Int32:
if (!sourceType.IsEnum)
tryFormatResult = XmlConvert.TryFormat((int)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Boolean:
tryFormatResult = XmlConvert.TryFormat((bool)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int16:
tryFormatResult = XmlConvert.TryFormat((short)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Int64:
tryFormatResult = XmlConvert.TryFormat((long)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Single:
tryFormatResult = XmlConvert.TryFormat((float)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Double:
tryFormatResult = XmlConvert.TryFormat((double)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Decimal:
tryFormatResult = XmlConvert.TryFormat((decimal)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.DateTime:
tryFormatResult = XmlConvert.TryFormat((DateTime)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Byte:
tryFormatResult = XmlConvert.TryFormat((byte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.SByte:
tryFormatResult = XmlConvert.TryFormat((sbyte)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt16:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt32:
tryFormatResult = XmlConvert.TryFormat((uint)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.UInt64:
tryFormatResult = XmlConvert.TryFormat((ulong)value, _primitivesBuffer, out charsWritten);
break;
case TypeCode.Char:
tryFormatResult = XmlConvert.TryFormat((ushort)value, _primitivesBuffer, out charsWritten);
break;
default:
//Guid is not supported in XmlUntypedConverter.Untyped and throws exception
//if (sourceType == typeof(Guid))
//{
// WriteWithBuffer((Guid)value, XmlConvert.TryFormat);
// break;
//}
if (sourceType == typeof(TimeSpan))
{
tryFormatResult = XmlConvert.TryFormat((TimeSpan)value, _primitivesBuffer, out charsWritten);
}
if (sourceType == typeof(DateTimeOffset))
{
tryFormatResult = XmlConvert.TryFormat((DateTimeOffset)value, _primitivesBuffer, out charsWritten);
}
break;
}

if (tryFormatResult)
WriteChars(_primitivesBuffer, 0, charsWritten);
else
base.WriteValue(value);
}

public override void WriteValue(bool value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(int value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(long value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(double value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(float value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(decimal value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

public override void WriteValue(DateTime value)
{
XmlConvert.TryFormat(value, _primitivesBuffer, out int charsWritten);
WriteChars(_primitivesBuffer, 0, charsWritten);
}

// Returns the state of the XmlWriter.
public override WriteState WriteState
{
Expand Down
Loading