From 9fc54c0e749d95e1999aed53e622ef6cc8301c47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:49:32 +0000 Subject: [PATCH 1/5] Initial plan From d2c7b3e7cb76f4362b383a46ba4966cdca2a6b68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:11:10 +0000 Subject: [PATCH 2/5] Add async XmlReader methods to OpenXmlReader and OpenXmlPartReader - Add virtual async methods (ReadAsync, ReadFirstChildAsync, ReadNextSiblingAsync, SkipAsync) to OpenXmlReader base class, guarded by FEATURE_ASYNC_SAX_XML - Add Async property to OpenXmlPartReaderOptions (FEATURE_ASYNC_SAX_XML only) - Update OpenXmlPartReader.CreateReader to accept OpenXmlPartReaderOptions and pass Async to XmlReaderSettings - Add async override implementations in OpenXmlPartReader using XmlReader.ReadAsync, SkipAsync, MoveToContentAsync - Add ReadAsync, SkipAsync, MoveToContentAsync overrides in XmlConvertingReader to delegate to BaseReader - Update PublicAPI unshipped files for netstandard2.0, net6.0, net8.0, net10.0 - Add OpenXmlReaderAsyncTest with 13 async tests covering both OpenXmlPartReader and OpenXmlDomReader scenarios Agent-Logs-Url: https://github.com/dotnet/Open-XML-SDK/sessions/1107ecf6-458c-498d-b45c-f7b3d6eeb5fc Co-authored-by: twsouthwick <583206+twsouthwick@users.noreply.github.com> --- .../OpenXmlPartReader.cs | 303 ++++++++++++++++- .../OpenXmlPartReaderOptions.cs | 12 +- .../OpenXmlReader.cs | 43 +++ .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 10 + .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 12 + .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 10 + .../netstandard2.0/PublicAPI.Unshipped.txt | 12 + .../XmlConvertingReader.cs | 14 + .../ofapiTest/OpenXmlReaderAsyncTest.cs | 318 ++++++++++++++++++ 9 files changed, 728 insertions(+), 6 deletions(-) create mode 100644 src/DocumentFormat.OpenXml.Framework/PublicAPI/net6.0/PublicAPI.Unshipped.txt create mode 100644 src/DocumentFormat.OpenXml.Framework/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt create mode 100644 test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs index b5f6abbba..778acdcfd 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 FEATURE_ASYNC_SAX_XML +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 FEATURE_ASYNC_SAX_XML + Async = options.Async, #endif }; @@ -896,5 +902,292 @@ private void ThrowIfEof() throw new InvalidOperationException(ExceptionMessages.ReaderInEofState); } } + +#if FEATURE_ASYNC_SAX_XML + // 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..25270603c 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 FEATURE_ASYNC_SAX_XML + /// + /// 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 FEATURE_ASYNC_SAX_XML + Async = Async, +#endif }; } diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs index 5cf655c24..38c7749f6 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 FEATURE_ASYNC_SAX_XML +using System.Threading.Tasks; +#endif namespace DocumentFormat.OpenXml { @@ -235,6 +238,46 @@ public virtual bool HasAttributes /// public abstract void Close(); +#if FEATURE_ASYNC_SAX_XML + /// + /// 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/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..6826c77a9 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 FEATURE_ASYNC_SAX_XML +using System.Threading.Tasks; +#endif namespace DocumentFormat.OpenXml { @@ -188,5 +191,16 @@ private string ApplyStrictTranslation(OpenXmlNamespace ns) return ns.Uri; } + +#if FEATURE_ASYNC_SAX_XML + /// + 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..e214c1b74 --- /dev/null +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs @@ -0,0 +1,318 @@ +// 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 FEATURE_ASYNC_SAX_XML + [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 + } +} From 94cd2cac4b1b06c0c235cd3b26458a8d41cafe0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:58:01 +0000 Subject: [PATCH 3/5] Fix SA1515 StyleCop errors: add blank lines before single-line comments in test file Agent-Logs-Url: https://github.com/dotnet/Open-XML-SDK/sessions/6f63d95d-60c0-4875-bf32-d65922638da7 Co-authored-by: mikeebowen <10384982+mikeebowen@users.noreply.github.com> --- .../ofapiTest/OpenXmlReaderAsyncTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs index e214c1b74..9e2316134 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs @@ -102,8 +102,10 @@ public async Task ReadFirstChildAsync_ShouldReturnFalse_WhenNoChildren() // 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(); From 59c3b0a3d5f3931c056f2eb57c769080c998fe2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 21:39:55 +0000 Subject: [PATCH 4/5] Refactor async tests to use traditional using blocks with braces Agent-Logs-Url: https://github.com/dotnet/Open-XML-SDK/sessions/9f7dafae-f19c-4860-9edb-b30d991726ef Co-authored-by: mikeebowen <10384982+mikeebowen@users.noreply.github.com> --- .../ofapiTest/OpenXmlReaderAsyncTest.cs | 369 ++++++++++-------- 1 file changed, 200 insertions(+), 169 deletions(-) diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs index 9e2316134..dec8395fb 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs @@ -19,190 +19,211 @@ public class OpenXmlReaderAsyncTest 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 }); + 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(); - bool result = await reader.ReadAsync(); + // 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); + // 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(); + 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 }); + // Act + using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true })) + { + // Read through all elements + while (await reader.ReadAsync()) + { + } - // Read through all elements - while (await reader.ReadAsync()) - { + // Assert + Assert.True(reader.EOF); + } } - - // 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 }); + 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(); - // Move to Document element - await reader.ReadAsync(); + // 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(); + // Move to first child (Body) + bool result = await reader.ReadFirstChildAsync(); - // Assert - Assert.True(result); - Assert.True(reader.IsStartElement); - Assert.Equal(typeof(Body), reader.ElementType); + // 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 }); + 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(); - // Move to Document element - await reader.ReadAsync(); + // Act + using (OpenXmlPartReader reader = new OpenXmlPartReader(mdp, new OpenXmlPartReaderOptions { Async = true })) + { + // Move to Document element + await reader.ReadAsync(); - // Move to Body - await reader.ReadFirstChildAsync(); + // Move to Body + await reader.ReadFirstChildAsync(); - // Try to move to first child of Body (should fail - empty Body) - bool result = 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); + // 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 }); + 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(); - // Move to Document -> Body -> first Paragraph - await reader.ReadAsync(); - await reader.ReadFirstChildAsync(); - await reader.ReadFirstChildAsync(); + // 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); + Assert.Equal(typeof(Paragraph), reader.ElementType); - // Move to second Paragraph (next sibling) - bool result = await reader.ReadNextSiblingAsync(); + // 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); + // 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 }); + 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(); - // Move to Document -> Body -> Paragraph - await reader.ReadAsync(); - await reader.ReadFirstChildAsync(); - await reader.ReadFirstChildAsync(); + // 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); + Assert.Equal(typeof(Paragraph), reader.ElementType); - // Try to move to next sibling (should be the Body end element) - bool result = await reader.ReadNextSiblingAsync(); + // 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); + // 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 }); + 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(); - // Move to Document -> Body - await reader.ReadAsync(); - await reader.ReadFirstChildAsync(); + // 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); + Assert.Equal(typeof(Body), reader.ElementType); - // Move to first Paragraph - await reader.ReadFirstChildAsync(); - Assert.Equal(typeof(Paragraph), 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(); + // 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); + // Should now be at the second Paragraph + Assert.True(reader.IsStartElement); + Assert.Equal(typeof(Paragraph), reader.ElementType); + } + } } [Fact] @@ -213,14 +234,15 @@ public async Task ReadAsync_DomReader_ShouldUseBaseVirtualImplementation() Paragraph para = new Paragraph(paragraphOuterXml); // Act - using OpenXmlReader reader = OpenXmlReader.Create(para); - - bool result = await reader.ReadAsync(); + 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); + // Assert + Assert.True(result); + Assert.True(reader.IsStartElement); + Assert.Equal(typeof(Paragraph), reader.ElementType); + } } [Fact] @@ -231,15 +253,17 @@ public async Task ReadFirstChildAsync_DomReader_ShouldUseBaseVirtualImplementati Body body = new Body(bodyOuterXml); // Act - using OpenXmlReader reader = OpenXmlReader.Create(body); - await reader.ReadAsync(); // move to Body + using (OpenXmlReader reader = OpenXmlReader.Create(body)) + { + await reader.ReadAsync(); // move to Body - bool result = await reader.ReadFirstChildAsync(); // move to Paragraph + bool result = await reader.ReadFirstChildAsync(); // move to Paragraph - // Assert - Assert.True(result); - Assert.True(reader.IsStartElement); - Assert.Equal(typeof(Paragraph), reader.ElementType); + // Assert + Assert.True(result); + Assert.True(reader.IsStartElement); + Assert.Equal(typeof(Paragraph), reader.ElementType); + } } [Fact] @@ -249,16 +273,18 @@ public async Task ReadNextSiblingAsync_DomReader_ShouldUseBaseVirtualImplementat 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 + 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 + bool result = await reader.ReadNextSiblingAsync(); // move to second Paragraph - // Assert - Assert.True(result); - Assert.True(reader.IsStartElement); - Assert.Equal(typeof(Paragraph), reader.ElementType); + // Assert + Assert.True(result); + Assert.True(reader.IsStartElement); + Assert.Equal(typeof(Paragraph), reader.ElementType); + } } [Fact] @@ -268,42 +294,47 @@ public async Task SkipAsync_DomReader_ShouldUseBaseVirtualImplementation() 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 + 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 + await reader.SkipAsync(); // skip first Paragraph - // Should now be at second Paragraph - Assert.True(reader.IsStartElement); - Assert.Equal(typeof(Paragraph), reader.ElementType); + // 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()) + using (MemoryStream memoryStream = new MemoryStream()) + using (WordprocessingDocument wpd = WordprocessingDocument.Create(memoryStream, WordprocessingDocumentType.Document)) { - if (reader.IsStartElement) + 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 })) { - elementCount++; + int elementCount = 0; + while (await reader.ReadAsync()) + { + if (reader.IsStartElement) + { + elementCount++; + } + } + + // Assert: Document, Body, Paragraph, Run, Text = 5 start elements + Assert.Equal(5, elementCount); } } - - // Assert: Document, Body, Paragraph, Run, Text = 5 start elements - Assert.Equal(5, elementCount); } [Fact] From 7aa9cc014fac4d4a0a706b7d97233d8cf2662feb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:05:23 +0000 Subject: [PATCH 5/5] Rename FEATURE_ASYNC_SAX_XML to TASKS_SUPPORTED throughout Agent-Logs-Url: https://github.com/dotnet/Open-XML-SDK/sessions/b4206f2b-8af0-42de-91b1-11dbdb2a70a2 Co-authored-by: mikeebowen <10384982+mikeebowen@users.noreply.github.com> --- Directory.Build.props | 2 +- src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs | 6 +++--- .../OpenXmlPartReaderOptions.cs | 4 ++-- src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriter.cs | 8 ++++---- .../OpenXmlPartWriterSettings.cs | 2 +- src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs | 4 ++-- src/DocumentFormat.OpenXml.Framework/OpenXmlWriter.cs | 4 ++-- .../XmlConvertingReader.cs | 4 ++-- .../ofapiTest/OpenXmlReaderAsyncTest.cs | 2 +- .../ofapiTest/OpenXmlWriterTest.cs | 2 +- 10 files changed, 19 insertions(+), 19 deletions(-) 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 778acdcfd..2fd94e502 100644 --- a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs +++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReader.cs @@ -11,7 +11,7 @@ using System.IO; using System.Linq; using System.Xml; -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED using System.Threading.Tasks; #endif @@ -682,7 +682,7 @@ private XmlReader CreateReader(Stream partStream, OpenXmlPartReaderOptions optio #else DtdProcessing = DtdProcessing.Prohibit, #endif -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED Async = options.Async, #endif }; @@ -903,7 +903,7 @@ private void ThrowIfEof() } } -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED // Async Methods /// diff --git a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs index 25270603c..576b27955 100644 --- a/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs +++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlPartReaderOptions.cs @@ -31,7 +31,7 @@ public struct OpenXmlPartReaderOptions /// public bool CloseStream { get; set; } -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED /// /// Gets or sets a value indicating whether asynchronous methods can be used. /// @@ -44,7 +44,7 @@ public struct OpenXmlPartReaderOptions MaxCharactersInPart = MaxCharactersInPart != 0 ? MaxCharactersInPart : part.MaxCharactersInPart, IgnoreWhitespace = IgnoreWhitespace, CloseStream = true, -#if FEATURE_ASYNC_SAX_XML +#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 38c7749f6..23c9ec83d 100644 --- a/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs +++ b/src/DocumentFormat.OpenXml.Framework/OpenXmlReader.cs @@ -7,7 +7,7 @@ using System.Collections.ObjectModel; using System.IO; using System.Xml; -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED using System.Threading.Tasks; #endif @@ -238,7 +238,7 @@ public virtual bool HasAttributes /// public abstract void Close(); -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED /// /// Asynchronously moves to read the next element. /// 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/XmlConvertingReader.cs b/src/DocumentFormat.OpenXml.Framework/XmlConvertingReader.cs index 6826c77a9..d71bd6ac9 100644 --- a/src/DocumentFormat.OpenXml.Framework/XmlConvertingReader.cs +++ b/src/DocumentFormat.OpenXml.Framework/XmlConvertingReader.cs @@ -5,7 +5,7 @@ using DocumentFormat.OpenXml.Framework; using System; using System.Xml; -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED using System.Threading.Tasks; #endif @@ -192,7 +192,7 @@ private string ApplyStrictTranslation(OpenXmlNamespace ns) return ns.Uri; } -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED /// public override Task ReadAsync() => BaseReader.ReadAsync(); diff --git a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs index dec8395fb..3cb338bca 100644 --- a/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs +++ b/test/DocumentFormat.OpenXml.Tests/ofapiTest/OpenXmlReaderAsyncTest.cs @@ -14,7 +14,7 @@ namespace DocumentFormat.OpenXml.Tests /// public class OpenXmlReaderAsyncTest { -#if FEATURE_ASYNC_SAX_XML +#if TASKS_SUPPORTED [Fact] public async Task ReadAsync_ShouldNavigateToRootElement() { 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() {