diff --git a/Directory.Build.props b/Directory.Build.props
index 5403640d8..630b778da 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -70,7 +70,7 @@
net10.0
net8.0;net10.0
$(SamplesFrameworks);net472
- $(DefineConstants);FEATURE_ASYNC_SAX_XML
+ $(DefineConstants);TASKS_SUPPORTED
diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs
index b5f6abbba..2fd94e502 100644
--- a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs
+++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs
@@ -11,6 +11,9 @@
using System.IO;
using System.Linq;
using System.Xml;
+#if TASKS_SUPPORTED
+using System.Threading.Tasks;
+#endif
namespace DocumentFormat.OpenXml
{
@@ -100,7 +103,7 @@ public OpenXmlPartReader(Stream partStream, IFeatureCollection features, OpenXml
_resolver = features.GetRequired();
_rootElements = features.GetRequired();
- _xmlReader = CreateReader(partStream, options.CloseStream, options.MaxCharactersInPart, ignoreWhitespace: options.IgnoreWhitespace, out _standalone, out _encoding);
+ _xmlReader = CreateReader(partStream, options, out _standalone, out _encoding);
}
///
@@ -667,17 +670,20 @@ public override void Close()
_xmlReader.Close();
}
- private XmlReader CreateReader(Stream partStream, bool closeInput, long maxCharactersInPart, bool ignoreWhitespace, out bool? standalone, out string? encoding)
+ private XmlReader CreateReader(Stream partStream, OpenXmlPartReaderOptions options, out bool? standalone, out string? encoding)
{
var settings = new XmlReaderSettings
{
- MaxCharactersInDocument = maxCharactersInPart,
- CloseInput = closeInput,
- IgnoreWhitespace = ignoreWhitespace,
+ MaxCharactersInDocument = options.MaxCharactersInPart,
+ CloseInput = options.CloseStream,
+ IgnoreWhitespace = options.IgnoreWhitespace,
#if NET35
ProhibitDtd = true,
#else
DtdProcessing = DtdProcessing.Prohibit,
+#endif
+#if TASKS_SUPPORTED
+ Async = options.Async,
#endif
};
@@ -896,5 +902,292 @@ private void ThrowIfEof()
throw new InvalidOperationException(ExceptionMessages.ReaderInEofState);
}
}
+
+#if TASKS_SUPPORTED
+ // Async Methods
+
+ ///
+ public override async Task ReadAsync()
+ {
+ ThrowIfObjectDisposed();
+ bool result = await MoveToNextElementAsync().ConfigureAwait(false);
+
+ if (result && !ReadMiscNodes)
+ {
+ // skip miscellaneous node
+ while (result && IsMiscNode)
+ {
+ result = await MoveToNextElementAsync().ConfigureAwait(false);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ public override async Task ReadFirstChildAsync()
+ {
+ ThrowIfObjectDisposed();
+ bool result = await MoveToFirstChildAsync().ConfigureAwait(false);
+
+ if (result && !ReadMiscNodes)
+ {
+ // skip miscellaneous node
+ while (result && IsMiscNode)
+ {
+ result = await MoveToNextSiblingInternalAsync().ConfigureAwait(false);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ public override async Task ReadNextSiblingAsync()
+ {
+ ThrowIfObjectDisposed();
+ bool result = await MoveToNextSiblingInternalAsync().ConfigureAwait(false);
+
+ if (result && !ReadMiscNodes)
+ {
+ // skip miscellaneous node
+ while (result && IsMiscNode)
+ {
+ result = await MoveToNextSiblingInternalAsync().ConfigureAwait(false);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ public override async Task SkipAsync()
+ {
+ ThrowIfObjectDisposed();
+ await InnerSkipAsync().ConfigureAwait(false);
+
+ if (!EOF && !ReadMiscNodes)
+ {
+ // skip miscellaneous node
+ while (!EOF && IsMiscNode)
+ {
+ await InnerSkipAsync().ConfigureAwait(false);
+ }
+ }
+ }
+
+ private async Task ReadRootAsync()
+ {
+ Debug.Assert(_elementState == ElementState.Null);
+ Debug.Assert(_elementStack.Count == 0);
+
+ await _xmlReader.MoveToContentAsync().ConfigureAwait(false);
+
+ while (!_xmlReader.EOF && _xmlReader.NodeType != XmlNodeType.Element)
+ {
+ await _xmlReader.SkipAsync().ConfigureAwait(false);
+ }
+
+ if (_xmlReader.EOF || !_xmlReader.IsStartElement())
+ {
+ throw new InvalidDataException(ExceptionMessages.PartIsEmpty);
+ }
+
+ // create the root element object
+ var rootElement = CreateElement(new(_xmlReader.NamespaceURI, _xmlReader.LocalName));
+
+ if (rootElement is null)
+ {
+ throw new InvalidDataException(ExceptionMessages.PartUnknown);
+ }
+
+ _elementStack.Push(rootElement);
+
+ LoadAttributes();
+
+ if (_xmlReader.IsEmptyElement)
+ {
+ _elementState = ElementState.LeafStart;
+ rootElement.Load(_xmlReader, OpenXmlLoadMode.Full);
+ }
+ else
+ {
+ _elementState = ElementState.Start;
+ }
+
+ return true;
+ }
+
+ private async Task MoveToNextElementAsync()
+ {
+ switch (_elementState)
+ {
+ case ElementState.Null:
+ return await ReadRootAsync().ConfigureAwait(false);
+
+ case ElementState.EOF:
+ return false;
+
+ case ElementState.Start:
+ break;
+
+ case ElementState.End:
+ case ElementState.MiscNode:
+ // cursor is end element, pop stack
+ _elementStack.Pop();
+ if (_elementStack.Count == 0)
+ {
+ _elementState = ElementState.EOF;
+ return false;
+ }
+
+ break;
+
+ case ElementState.LeafStart:
+ // cursor is leaf element start
+ // just change the state to element end
+ // do not move the cursor
+ _elementState = ElementState.LeafEnd;
+ return true;
+
+ case ElementState.LeafEnd:
+ case ElementState.LoadEnd:
+ // cursor is end element, pop stack
+ _elementStack.Pop();
+ if (_elementStack.Count == 0)
+ {
+ _elementState = ElementState.EOF;
+ return false;
+ }
+ else
+ {
+ GetElementInformation();
+ return true;
+ }
+
+ default:
+ break;
+ }
+
+ _elementState = ElementState.Null;
+
+ if (_xmlReader.EOF || !await _xmlReader.ReadAsync().ConfigureAwait(false))
+ {
+ _elementState = ElementState.EOF;
+ return false;
+ }
+ else
+ {
+ GetElementInformation();
+ return true;
+ }
+ }
+
+ private async Task MoveToFirstChildAsync()
+ {
+ switch (_elementState)
+ {
+ case ElementState.EOF:
+ return false;
+
+ case ElementState.Start:
+ if (!await _xmlReader.ReadAsync().ConfigureAwait(false))
+ {
+ // should be able to read.
+ Debug.Assert(false);
+ return false;
+ }
+
+ GetElementInformation();
+ if (_elementState == ElementState.End)
+ {
+ return false;
+ }
+
+ return true;
+
+ case ElementState.LeafStart:
+ _elementState = ElementState.LeafEnd;
+ return false;
+
+ case ElementState.End:
+ case ElementState.LeafEnd:
+ case ElementState.LoadEnd:
+ case ElementState.MiscNode:
+ return false;
+
+ case ElementState.Null:
+ ThrowIfNull();
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ private async Task MoveToNextSiblingInternalAsync()
+ {
+ Debug.Assert(_xmlReader is not null);
+
+ if (_elementState == ElementState.EOF)
+ {
+ return false;
+ }
+
+ await InnerSkipAsync().ConfigureAwait(false);
+
+ if (_elementState == ElementState.EOF)
+ {
+ return false;
+ }
+ else if (_elementState == ElementState.End)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ private async Task InnerSkipAsync()
+ {
+ switch (_elementState)
+ {
+ case ElementState.Null:
+ ThrowIfNull();
+ break;
+
+ case ElementState.EOF:
+ return;
+
+ case ElementState.Start:
+ case ElementState.End:
+ case ElementState.MiscNode:
+ await _xmlReader.SkipAsync().ConfigureAwait(false);
+ _elementStack.Pop();
+ GetElementInformation();
+ return;
+
+ case ElementState.LeafStart:
+ // no move, just process cursor
+ _elementStack.Pop();
+ GetElementInformation();
+ return;
+
+ case ElementState.LeafEnd:
+ case ElementState.LoadEnd:
+ // cursor is leaf element, pop stack, no move
+ _elementStack.Pop();
+ GetElementInformation();
+ return;
+
+ default:
+ break;
+ }
+ }
+#endif
}
}
diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs
index 53c3c64f0..576b27955 100644
--- a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs
+++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using DocumentFormat.OpenXml.Packaging;
@@ -31,11 +31,21 @@ public struct OpenXmlPartReaderOptions
///
public bool CloseStream { get; set; }
+#if TASKS_SUPPORTED
+ ///
+ /// Gets or sets a value indicating whether asynchronous methods can be used.
+ ///
+ public bool Async { get; set; }
+#endif
+
internal OpenXmlPartReaderOptions UpdateForPart(OpenXmlPart part) => new()
{
ReadMiscellaneousNodes = ReadMiscellaneousNodes,
MaxCharactersInPart = MaxCharactersInPart != 0 ? MaxCharactersInPart : part.MaxCharactersInPart,
IgnoreWhitespace = IgnoreWhitespace,
CloseStream = true,
+#if TASKS_SUPPORTED
+ Async = Async,
+#endif
};
}
diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriter.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriter.cs
index 7dbb47209..141e41106 100644
--- a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriter.cs
+++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriter.cs
@@ -6,7 +6,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
using DocumentFormat.OpenXml.Framework;
using System.Threading.Tasks;
#endif
@@ -82,7 +82,7 @@ public OpenXmlPartWriter(OpenXmlPart openXmlPart, OpenXmlPartWriterSettings sett
{
CloseOutput = true,
Encoding = settings.Encoding,
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
Async = settings.Async,
#endif
};
@@ -146,7 +146,7 @@ public OpenXmlPartWriter(Stream partStream, OpenXmlPartWriterSettings settings)
{
CloseOutput = settings.CloseOutput,
Encoding = settings.Encoding,
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
Async = settings.Async,
#endif
};
@@ -430,7 +430,7 @@ public override void Close()
#endregion
// Async Methods
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
///
/// Asynchronously writes the XML declaration with the version "1.0".
///
diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriterSettings.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriterSettings.cs
index 88e076308..c955f69c8 100644
--- a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriterSettings.cs
+++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriterSettings.cs
@@ -10,7 +10,7 @@ namespace DocumentFormat.OpenXml;
///
public class OpenXmlPartWriterSettings
{
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
///
/// Gets or sets a value indicating whether asynchronous methods can be used.
///
diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs
index 5cf655c24..23c9ec83d 100644
--- a/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs
+++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs
@@ -7,6 +7,9 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Xml;
+#if TASKS_SUPPORTED
+using System.Threading.Tasks;
+#endif
namespace DocumentFormat.OpenXml
{
@@ -235,6 +238,46 @@ public virtual bool HasAttributes
///
public abstract void Close();
+#if TASKS_SUPPORTED
+ ///
+ /// Asynchronously moves to read the next element.
+ ///
+ /// Returns true if the next element was read successfully; false if there are no more elements to read.
+ public virtual Task ReadAsync()
+ {
+ return Task.FromResult(Read());
+ }
+
+ ///
+ /// Asynchronously moves to read the first child element.
+ ///
+ /// Returns true if the first child element was read successfully; false if there are no child elements to read.
+ /// This method can only be called on element start. At the current node, the reader will move to the end tag if there is no child element.
+ public virtual Task ReadFirstChildAsync()
+ {
+ return Task.FromResult(ReadFirstChild());
+ }
+
+ ///
+ /// Asynchronously moves to read the next sibling element.
+ ///
+ /// Returns true if the next sibling element was read successfully; false if there are no more sibling elements to read.
+ /// At the current node, the reader will move to the end tag of the parent node if there are no more sibling elements.
+ public virtual Task ReadNextSiblingAsync()
+ {
+ return Task.FromResult(ReadNextSibling());
+ }
+
+ ///
+ /// Asynchronously skips the child elements of the current node.
+ ///
+ public virtual Task SkipAsync()
+ {
+ Skip();
+ return Task.CompletedTask;
+ }
+#endif
+
///
/// Thrown if the object is disposed.
///
diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlWriter.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlWriter.cs
index 24fa6af83..00c58299c 100644
--- a/src/DocumentFormat.OpenXml.Framework/OpenXmlWriter.cs
+++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlWriter.cs
@@ -6,7 +6,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
using System.Threading.Tasks;
#endif
@@ -131,7 +131,7 @@ protected OpenXmlWriter()
///
public abstract void Close();
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
///
/// Asynchronously writes the XML declaration with the version "1.0".
///
diff --git a/src/DocumentFormat.OpenXml.Framework/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/src/DocumentFormat.OpenXml.Framework/PublicAPI/net10.0/PublicAPI.Unshipped.txt
index e2eebeccc..362fd2601 100644
--- a/src/DocumentFormat.OpenXml.Framework/PublicAPI/net10.0/PublicAPI.Unshipped.txt
+++ b/src/DocumentFormat.OpenXml.Framework/PublicAPI/net10.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,12 @@
#nullable enable
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.SkipAsync() -> System.Threading.Tasks.Task!
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.get -> bool
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.set -> void
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.SkipAsync() -> System.Threading.Tasks.Task!
diff --git a/src/DocumentFormat.OpenXml.Framework/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/DocumentFormat.OpenXml.Framework/PublicAPI/net6.0/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000..362fd2601
--- /dev/null
+++ b/src/DocumentFormat.OpenXml.Framework/PublicAPI/net6.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,12 @@
+#nullable enable
+
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.SkipAsync() -> System.Threading.Tasks.Task!
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.get -> bool
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.set -> void
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.SkipAsync() -> System.Threading.Tasks.Task!
diff --git a/src/DocumentFormat.OpenXml.Framework/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/DocumentFormat.OpenXml.Framework/PublicAPI/net8.0/PublicAPI.Unshipped.txt
index e2eebeccc..362fd2601 100644
--- a/src/DocumentFormat.OpenXml.Framework/PublicAPI/net8.0/PublicAPI.Unshipped.txt
+++ b/src/DocumentFormat.OpenXml.Framework/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,12 @@
#nullable enable
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.SkipAsync() -> System.Threading.Tasks.Task!
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.get -> bool
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.set -> void
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.SkipAsync() -> System.Threading.Tasks.Task!
diff --git a/src/DocumentFormat.OpenXml.Framework/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/DocumentFormat.OpenXml.Framework/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000..362fd2601
--- /dev/null
+++ b/src/DocumentFormat.OpenXml.Framework/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,12 @@
+#nullable enable
+
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+virtual DocumentFormat.OpenXml.OpenXmlReader.SkipAsync() -> System.Threading.Tasks.Task!
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.get -> bool
+DocumentFormat.OpenXml.OpenXmlPartReaderOptions.Async.set -> void
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadFirstChildAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.ReadNextSiblingAsync() -> System.Threading.Tasks.Task!
+override DocumentFormat.OpenXml.OpenXmlPartReader.SkipAsync() -> System.Threading.Tasks.Task!
diff --git a/src/DocumentFormat.OpenXml.Framework/XmlConvertingReader.cs b/src/DocumentFormat.OpenXml.Framework/XmlConvertingReader.cs
index 76b3bd350..d71bd6ac9 100644
--- a/src/DocumentFormat.OpenXml.Framework/XmlConvertingReader.cs
+++ b/src/DocumentFormat.OpenXml.Framework/XmlConvertingReader.cs
@@ -5,6 +5,9 @@
using DocumentFormat.OpenXml.Framework;
using System;
using System.Xml;
+#if TASKS_SUPPORTED
+using System.Threading.Tasks;
+#endif
namespace DocumentFormat.OpenXml
{
@@ -188,5 +191,16 @@ private string ApplyStrictTranslation(OpenXmlNamespace ns)
return ns.Uri;
}
+
+#if TASKS_SUPPORTED
+ ///
+ public override Task ReadAsync() => BaseReader.ReadAsync();
+
+ ///
+ public override Task SkipAsync() => BaseReader.SkipAsync();
+
+ ///
+ public override Task MoveToContentAsync() => BaseReader.MoveToContentAsync();
+#endif
}
}
diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs
new file mode 100644
index 000000000..3cb338bca
--- /dev/null
+++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs
@@ -0,0 +1,351 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace DocumentFormat.OpenXml.Tests
+{
+ ///
+ /// Tests for async methods on and .
+ ///
+ public class OpenXmlReaderAsyncTest
+ {
+#if TASKS_SUPPORTED
+ [Fact]
+ public async Task ReadAsync_ShouldNavigateToRootElement()
+ {
+ // Arrange
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body(new Paragraph(new Run(new Text("Hello")))));
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ bool result = await reader.ReadAsync();
+
+ // Assert - should be at the Document element
+ Assert.True(result);
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Document), reader.ElementType);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ReadAsync_ShouldReturnFalse_WhenNoMoreElements()
+ {
+ // Arrange
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body());
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ // Read through all elements
+ while (await reader.ReadAsync())
+ {
+ }
+
+ // Assert
+ Assert.True(reader.EOF);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ReadFirstChildAsync_ShouldMoveToFirstChild()
+ {
+ // Arrange
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body(new Paragraph()));
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ // Move to Document element
+ await reader.ReadAsync();
+
+ // Move to first child (Body)
+ bool result = await reader.ReadFirstChildAsync();
+
+ // Assert
+ Assert.True(result);
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Body), reader.ElementType);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ReadFirstChildAsync_ShouldReturnFalse_WhenNoChildren()
+ {
+ // Arrange
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body());
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ // Move to Document element
+ await reader.ReadAsync();
+
+ // Move to Body
+ await reader.ReadFirstChildAsync();
+
+ // Try to move to first child of Body (should fail - empty Body)
+ bool result = await reader.ReadFirstChildAsync();
+
+ // Assert
+ Assert.False(result);
+ Assert.True(reader.IsEndElement);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ReadNextSiblingAsync_ShouldMoveToNextSibling()
+ {
+ // Arrange
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body(new Paragraph(), new Paragraph()));
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ // Move to Document -> Body -> first Paragraph
+ await reader.ReadAsync();
+ await reader.ReadFirstChildAsync();
+ await reader.ReadFirstChildAsync();
+
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+
+ // Move to second Paragraph (next sibling)
+ bool result = await reader.ReadNextSiblingAsync();
+
+ // Assert
+ Assert.True(result);
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ReadNextSiblingAsync_ShouldReturnFalse_WhenNoMoreSiblings()
+ {
+ // Arrange
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body(new Paragraph()));
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ // Move to Document -> Body -> Paragraph
+ await reader.ReadAsync();
+ await reader.ReadFirstChildAsync();
+ await reader.ReadFirstChildAsync();
+
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+
+ // Try to move to next sibling (should be the Body end element)
+ bool result = await reader.ReadNextSiblingAsync();
+
+ // Assert - no more siblings, moved to end element of parent
+ Assert.False(result);
+ Assert.True(reader.IsEndElement);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task SkipAsync_ShouldSkipCurrentElementChildren()
+ {
+ // Arrange
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body(new Paragraph(new Run(new Text("Hello"))), new Paragraph()));
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ // Move to Document -> Body
+ await reader.ReadAsync();
+ await reader.ReadFirstChildAsync();
+
+ Assert.Equal(typeof(Body), reader.ElementType);
+
+ // Move to first Paragraph
+ await reader.ReadFirstChildAsync();
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+
+ // Skip the first Paragraph (and its children)
+ await reader.SkipAsync();
+
+ // Should now be at the second Paragraph
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task ReadAsync_DomReader_ShouldUseBaseVirtualImplementation()
+ {
+ // Arrange - OpenXmlDomReader uses the base virtual implementation
+ string paragraphOuterXml = "Run Text.";
+ Paragraph para = new Paragraph(paragraphOuterXml);
+
+ // Act
+ using (OpenXmlReader reader = OpenXmlReader.Create(para))
+ {
+ bool result = await reader.ReadAsync();
+
+ // Assert
+ Assert.True(result);
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+ }
+ }
+
+ [Fact]
+ public async Task ReadFirstChildAsync_DomReader_ShouldUseBaseVirtualImplementation()
+ {
+ // Arrange - OpenXmlDomReader uses the base virtual implementation
+ string bodyOuterXml = "";
+ Body body = new Body(bodyOuterXml);
+
+ // Act
+ using (OpenXmlReader reader = OpenXmlReader.Create(body))
+ {
+ await reader.ReadAsync(); // move to Body
+
+ bool result = await reader.ReadFirstChildAsync(); // move to Paragraph
+
+ // Assert
+ Assert.True(result);
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+ }
+ }
+
+ [Fact]
+ public async Task ReadNextSiblingAsync_DomReader_ShouldUseBaseVirtualImplementation()
+ {
+ // Arrange - OpenXmlDomReader uses the base virtual implementation
+ Body body = new Body(new Paragraph(), new Paragraph());
+
+ // Act
+ using (OpenXmlReader reader = OpenXmlReader.Create(body))
+ {
+ await reader.ReadAsync(); // move to Body
+ await reader.ReadFirstChildAsync(); // move to first Paragraph
+
+ bool result = await reader.ReadNextSiblingAsync(); // move to second Paragraph
+
+ // Assert
+ Assert.True(result);
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+ }
+ }
+
+ [Fact]
+ public async Task SkipAsync_DomReader_ShouldUseBaseVirtualImplementation()
+ {
+ // Arrange - OpenXmlDomReader uses the base virtual implementation
+ Body body = new Body(new Paragraph(new Run(new Text("Hello"))), new Paragraph());
+
+ // Act
+ using (OpenXmlReader reader = OpenXmlReader.Create(body))
+ {
+ await reader.ReadAsync(); // move to Body
+ await reader.ReadFirstChildAsync(); // move to first Paragraph
+
+ await reader.SkipAsync(); // skip first Paragraph
+
+ // Should now be at second Paragraph
+ Assert.True(reader.IsStartElement);
+ Assert.Equal(typeof(Paragraph), reader.ElementType);
+ }
+ }
+
+ [Fact]
+ public async Task ReadAsync_PartReader_ShouldReadAllElements()
+ {
+ // Arrange - full traversal of a document
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document))
+ {
+ MainDocumentPart mdp = wpd.AddMainDocumentPart();
+ mdp.Document = new Document(new Body(new Paragraph(new Run(new Text("Hello World")))));
+ mdp.Document.Save();
+ wpd.Save();
+
+ // Act
+ using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true }))
+ {
+ int elementCount = 0;
+ while (await reader.ReadAsync())
+ {
+ if (reader.IsStartElement)
+ {
+ elementCount++;
+ }
+ }
+
+ // Assert: Document, Body, Paragraph, Run, Text = 5 start elements
+ Assert.Equal(5, elementCount);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task OpenXmlPartReaderOptions_Async_DefaultsFalse()
+ {
+ // Arrange
+ OpenXmlPartReaderOptions options = default;
+
+ // Assert
+ Assert.False(options.Async);
+ }
+#endif
+ }
+}
diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlWriterTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlWriterTest.cs
index 60d4f83e1..4a3b929a7 100644
--- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlWriterTest.cs
+++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlWriterTest.cs
@@ -239,7 +239,7 @@ public void WriteStringExceptionTest7()
}
}
-#if FEATURE_ASYNC_SAX_XML
+#if TASKS_SUPPORTED
[Fact]
public async Task WriteStartDocumentAsync_ShouldWriteStartDocument()
{