|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Threading.Tasks;
using System.Xml.Schema;
using System.Xml.XPath;
namespace System.Xml
{
// Represents a writer that provides fast non-cached forward-only way of generating XML streams containing XML documents
// that conform to the W3C Extensible Markup Language (XML) 1.0 specification and the Namespaces in XML specification.
public abstract partial class XmlWriter : IDisposable, IAsyncDisposable
{
// Write methods
// Writes out the XML declaration with the version "1.0".
public virtual Task WriteStartDocumentAsync()
{
throw new NotImplementedException();
}
//Writes out the XML declaration with the version "1.0" and the specified standalone attribute.
public virtual Task WriteStartDocumentAsync(bool standalone)
{
throw new NotImplementedException();
}
//Closes any open elements or attributes and puts the writer back in the Start state.
public virtual Task WriteEndDocumentAsync()
{
throw new NotImplementedException();
}
// Writes out the DOCTYPE declaration with the specified name and optional attributes.
public virtual Task WriteDocTypeAsync(string name, string? pubid, string? sysid, string? subset)
{
throw new NotImplementedException();
}
// Writes out the specified start tag and associates it with the given namespace and prefix.
public virtual Task WriteStartElementAsync(string? prefix, string localName, string? ns)
{
throw new NotImplementedException();
}
// Closes one element and pops the corresponding namespace scope.
public virtual Task WriteEndElementAsync()
{
throw new NotImplementedException();
}
// Closes one element and pops the corresponding namespace scope. Writes out a full end element tag, e.g. </element>.
public virtual Task WriteFullEndElementAsync()
{
throw new NotImplementedException();
}
// Writes out the attribute with the specified prefix, LocalName, NamespaceURI and value.
public Task WriteAttributeStringAsync(string? prefix, string localName, string? ns, string? value)
{
Task task = WriteStartAttributeAsync(prefix, localName, ns);
if (task.IsSuccess())
{
return WriteStringAsync(value).CallTaskFuncWhenFinishAsync(thisRef => thisRef.WriteEndAttributeAsync(), this);
}
return WriteAttributeStringAsyncHelper(task, value);
}
private async Task WriteAttributeStringAsyncHelper(Task task, string? value)
{
await task.ConfigureAwait(false);
await WriteStringAsync(value).ConfigureAwait(false);
await WriteEndAttributeAsync().ConfigureAwait(false);
}
// Writes the start of an attribute.
protected internal virtual Task WriteStartAttributeAsync(string? prefix, string localName, string? ns)
{
throw new NotImplementedException();
}
// Closes the attribute opened by WriteStartAttribute call.
protected internal virtual Task WriteEndAttributeAsync()
{
throw new NotImplementedException();
}
// Writes out a <![CDATA[...]]>; block containing the specified text.
public virtual Task WriteCDataAsync(string? text)
{
throw new NotImplementedException();
}
// Writes out a comment <!--...-->; containing the specified text.
public virtual Task WriteCommentAsync(string? text)
{
throw new NotImplementedException();
}
// Writes out a processing instruction with a space between the name and text as follows: <?name text?>
public virtual Task WriteProcessingInstructionAsync(string name, string? text)
{
throw new NotImplementedException();
}
// Writes out an entity reference as follows: "&"+name+";".
public virtual Task WriteEntityRefAsync(string name)
{
throw new NotImplementedException();
}
// Forces the generation of a character entity for the specified Unicode character value.
public virtual Task WriteCharEntityAsync(char ch)
{
throw new NotImplementedException();
}
// Writes out the given whitespace.
public virtual Task WriteWhitespaceAsync(string? ws)
{
throw new NotImplementedException();
}
// Writes out the specified text content.
public virtual Task WriteStringAsync(string? text)
{
throw new NotImplementedException();
}
// Write out the given surrogate pair as an entity reference.
public virtual Task WriteSurrogateCharEntityAsync(char lowChar, char highChar)
{
throw new NotImplementedException();
}
// Writes out the specified text content.
public virtual Task WriteCharsAsync(char[] buffer, int index, int count)
{
throw new NotImplementedException();
}
// Writes raw markup from the given character buffer.
public virtual Task WriteRawAsync(char[] buffer, int index, int count)
{
throw new NotImplementedException();
}
// Writes raw markup from the given string.
public virtual Task WriteRawAsync(string data)
{
throw new NotImplementedException();
}
// Encodes the specified binary bytes as base64 and writes out the resulting text.
public virtual Task WriteBase64Async(byte[] buffer, int index, int count)
{
throw new NotImplementedException();
}
// Encodes the specified binary bytes as bin hex and writes out the resulting text.
public virtual Task WriteBinHexAsync(byte[] buffer, int index, int count)
{
return BinHexEncoder.EncodeAsync(buffer, index, count, this);
}
// Flushes data that is in the internal buffers into the underlying streams/TextReader and flushes the stream/TextReader.
public virtual Task FlushAsync()
{
throw new NotImplementedException();
}
// Scalar Value Methods
// Writes out the specified name, ensuring it is a valid NmToken according to the XML specification
// (http://www.w3.org/TR/1998/REC-xml-19980210#NT-Name).
public virtual Task WriteNmTokenAsync(string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);
return WriteStringAsync(XmlConvert.VerifyNMTOKEN(name, ExceptionType.ArgumentException));
}
// Writes out the specified name, ensuring it is a valid Name according to the XML specification
// (http://www.w3.org/TR/1998/REC-xml-19980210#NT-Name).
public virtual Task WriteNameAsync(string name)
{
return WriteStringAsync(XmlConvert.VerifyQName(name, ExceptionType.ArgumentException));
}
// Writes out the specified namespace-qualified name by looking up the prefix that is in scope for the given namespace.
public virtual async Task WriteQualifiedNameAsync(string localName, string? ns)
{
if (!string.IsNullOrEmpty(ns))
{
string? prefix = LookupPrefix(ns);
if (prefix == null)
{
throw new ArgumentException(SR.Format(SR.Xml_UndefNamespace, ns));
}
await WriteStringAsync(prefix).ConfigureAwait(false);
await WriteStringAsync(":").ConfigureAwait(false);
}
await WriteStringAsync(localName).ConfigureAwait(false);
}
// XmlReader Helper Methods
// Writes out all the attributes found at the current position in the specified XmlReader.
public virtual Task WriteAttributesAsync(XmlReader reader, bool defattr)
{
ArgumentNullException.ThrowIfNull(reader);
return Core(reader, defattr);
async Task Core(XmlReader reader, bool defattr)
{
if (reader.NodeType is XmlNodeType.Element or XmlNodeType.XmlDeclaration)
{
if (reader.MoveToFirstAttribute())
{
await WriteAttributesAsync(reader, defattr).ConfigureAwait(false);
reader.MoveToElement();
}
}
else if (reader.NodeType != XmlNodeType.Attribute)
{
throw new XmlException(SR.Xml_InvalidPosition, string.Empty);
}
else
{
do
{
// we need to check both XmlReader.IsDefault and XmlReader.SchemaInfo.IsDefault.
// If either of these is true and defattr=false, we should not write the attribute out
if (defattr || !reader.IsDefaultInternal)
{
await WriteStartAttributeAsync(reader.Prefix, reader.LocalName, reader.NamespaceURI).ConfigureAwait(false);
while (reader.ReadAttributeValue())
{
if (reader.NodeType == XmlNodeType.EntityReference)
{
await WriteEntityRefAsync(reader.Name).ConfigureAwait(false);
}
else
{
await WriteStringAsync(reader.Value).ConfigureAwait(false);
}
}
await WriteEndAttributeAsync().ConfigureAwait(false);
}
}
while (reader.MoveToNextAttribute());
}
}
}
// Copies the current node from the given reader to the writer (including child nodes), and if called on an element moves the XmlReader
// to the corresponding end element.
public virtual Task WriteNodeAsync(XmlReader reader, bool defattr)
{
ArgumentNullException.ThrowIfNull(reader);
if (reader.Settings is { Async: true })
{
return WriteNodeAsync_CallAsyncReader(reader, defattr);
}
return WriteNodeAsync_CallSyncReader(reader, defattr);
}
// Copies the current node from the given reader to the writer (including child nodes), and if called on an element moves the XmlReader
// to the corresponding end element.
//use sync methods on the reader
internal async Task WriteNodeAsync_CallSyncReader(XmlReader reader, bool defattr)
{
bool canReadChunk = reader.CanReadValueChunk;
int d = reader.NodeType == XmlNodeType.None ? -1 : reader.Depth;
do
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
await WriteStartElementAsync(reader.Prefix, reader.LocalName, reader.NamespaceURI).ConfigureAwait(false);
await WriteAttributesAsync(reader, defattr).ConfigureAwait(false);
if (reader.IsEmptyElement)
{
await WriteEndElementAsync().ConfigureAwait(false);
}
break;
case XmlNodeType.Text:
if (canReadChunk)
{
_writeNodeBuffer ??= new char[WriteNodeBufferSize];
int read;
while ((read = reader.ReadValueChunk(_writeNodeBuffer, 0, WriteNodeBufferSize)) > 0)
{
await WriteCharsAsync(_writeNodeBuffer, 0, read).ConfigureAwait(false);
}
}
else
{
await WriteStringAsync(reader.Value).ConfigureAwait(false);
}
break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
await WriteWhitespaceAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.CDATA:
await WriteCDataAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EntityReference:
await WriteEntityRefAsync(reader.Name).ConfigureAwait(false);
break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
await WriteProcessingInstructionAsync(reader.Name, reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.DocumentType:
await WriteDocTypeAsync(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.Comment:
await WriteCommentAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EndElement:
await WriteFullEndElementAsync().ConfigureAwait(false);
break;
}
} while (reader.Read() && (d < reader.Depth || (d == reader.Depth && reader.NodeType == XmlNodeType.EndElement)));
}
// Copies the current node from the given reader to the writer (including child nodes), and if called on an element moves the XmlReader
// to the corresponding end element.
//use async methods on the reader
internal async Task WriteNodeAsync_CallAsyncReader(XmlReader reader, bool defattr)
{
bool canReadChunk = reader.CanReadValueChunk;
int d = reader.NodeType == XmlNodeType.None ? -1 : reader.Depth;
do
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
await WriteStartElementAsync(reader.Prefix, reader.LocalName, reader.NamespaceURI).ConfigureAwait(false);
await WriteAttributesAsync(reader, defattr).ConfigureAwait(false);
if (reader.IsEmptyElement)
{
await WriteEndElementAsync().ConfigureAwait(false);
}
break;
case XmlNodeType.Text:
if (canReadChunk)
{
_writeNodeBuffer ??= new char[WriteNodeBufferSize];
int read;
while ((read = await reader.ReadValueChunkAsync(_writeNodeBuffer, 0, WriteNodeBufferSize).ConfigureAwait(false)) > 0)
{
await WriteCharsAsync(_writeNodeBuffer, 0, read).ConfigureAwait(false);
}
}
else
{
//reader.Value may block on Text or WhiteSpace node, use GetValueAsync
await WriteStringAsync(await reader.GetValueAsync().ConfigureAwait(false)).ConfigureAwait(false);
}
break;
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
await WriteWhitespaceAsync(await reader.GetValueAsync().ConfigureAwait(false)).ConfigureAwait(false);
break;
case XmlNodeType.CDATA:
await WriteCDataAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EntityReference:
await WriteEntityRefAsync(reader.Name).ConfigureAwait(false);
break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
await WriteProcessingInstructionAsync(reader.Name, reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.DocumentType:
await WriteDocTypeAsync(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.Comment:
await WriteCommentAsync(reader.Value).ConfigureAwait(false);
break;
case XmlNodeType.EndElement:
await WriteFullEndElementAsync().ConfigureAwait(false);
break;
}
} while (await reader.ReadAsync().ConfigureAwait(false) && (d < reader.Depth || (d == reader.Depth && reader.NodeType == XmlNodeType.EndElement)));
}
// Copies the current node from the given XPathNavigator to the writer (including child nodes).
public virtual Task WriteNodeAsync(XPathNavigator navigator, bool defattr)
{
ArgumentNullException.ThrowIfNull(navigator);
return Core(navigator, defattr);
async Task Core(XPathNavigator navigator, bool defattr)
{
int iLevel = 0;
navigator = navigator.Clone();
while (true)
{
bool mayHaveChildren = false;
XPathNodeType nodeType = navigator.NodeType;
switch (nodeType)
{
case XPathNodeType.Element:
await WriteStartElementAsync(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI).ConfigureAwait(false);
// Copy attributes
if (navigator.MoveToFirstAttribute())
{
do
{
IXmlSchemaInfo? schemaInfo = navigator.SchemaInfo;
if (defattr || (schemaInfo == null || !schemaInfo.IsDefault))
{
await WriteStartAttributeAsync(navigator.Prefix, navigator.LocalName, navigator.NamespaceURI).ConfigureAwait(false);
// copy string value to writer
await WriteStringAsync(navigator.Value).ConfigureAwait(false);
await WriteEndAttributeAsync().ConfigureAwait(false);
}
} while (navigator.MoveToNextAttribute());
navigator.MoveToParent();
}
// Copy namespaces
if (navigator.MoveToFirstNamespace(XPathNamespaceScope.Local))
{
await WriteLocalNamespacesAsync(navigator).ConfigureAwait(false);
navigator.MoveToParent();
}
mayHaveChildren = true;
break;
case XPathNodeType.Attribute:
// do nothing on root level attribute
break;
case XPathNodeType.Text:
await WriteStringAsync(navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.SignificantWhitespace:
case XPathNodeType.Whitespace:
await WriteWhitespaceAsync(navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.Root:
mayHaveChildren = true;
break;
case XPathNodeType.Comment:
await WriteCommentAsync(navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.ProcessingInstruction:
await WriteProcessingInstructionAsync(navigator.LocalName, navigator.Value).ConfigureAwait(false);
break;
case XPathNodeType.Namespace:
// do nothing on root level namespace
break;
default:
Debug.Fail($"Unexpected node type {nodeType}");
break;
}
if (mayHaveChildren)
{
// If children exist, move down to next level
if (navigator.MoveToFirstChild())
{
iLevel++;
continue;
}
// EndElement
if (navigator.NodeType == XPathNodeType.Element)
{
if (navigator.IsEmptyElement)
{
await WriteEndElementAsync().ConfigureAwait(false);
}
else
{
await WriteFullEndElementAsync().ConfigureAwait(false);
}
}
}
// No children
while (true)
{
if (iLevel == 0)
{
// The entire subtree has been copied
return;
}
if (navigator.MoveToNext())
{
// Found a sibling, so break to outer loop
break;
}
// No siblings, so move up to previous level
iLevel--;
navigator.MoveToParent();
// EndElement
if (navigator.NodeType == XPathNodeType.Element)
await WriteFullEndElementAsync().ConfigureAwait(false);
}
}
}
}
// Element Helper Methods
// Writes out an attribute with the specified name, namespace URI, and string value.
public async Task WriteElementStringAsync(string? prefix, string localName, string? ns, string value)
{
await WriteStartElementAsync(prefix, localName, ns).ConfigureAwait(false);
if (!string.IsNullOrEmpty(value))
{
await WriteStringAsync(value).ConfigureAwait(false);
}
await WriteEndElementAsync().ConfigureAwait(false);
}
// Copy local namespaces on the navigator's current node to the raw writer. The namespaces are returned by the navigator in reversed order.
// The recursive call reverses them back.
private async Task WriteLocalNamespacesAsync(XPathNavigator nsNav)
{
string prefix = nsNav.LocalName;
string ns = nsNav.Value;
if (nsNav.MoveToNextNamespace(XPathNamespaceScope.Local))
{
await WriteLocalNamespacesAsync(nsNav).ConfigureAwait(false);
}
if (prefix.Length == 0)
{
await WriteAttributeStringAsync(string.Empty, "xmlns", XmlReservedNs.NsXmlNs, ns).ConfigureAwait(false);
}
else
{
await WriteAttributeStringAsync("xmlns", prefix, XmlReservedNs.NsXmlNs, ns).ConfigureAwait(false);
}
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(false);
GC.SuppressFinalize(this);
}
protected virtual ValueTask DisposeAsyncCore()
{
if (WriteState != WriteState.Closed)
{
Dispose(true);
}
return default;
}
}
}
|