Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9bf718b
WriteStartDocumentAsync works
mikeebowen Mar 14, 2025
f71c2f9
Merge branch 'main' into add-async-xmlwriter-methods-913
mikeebowen Mar 17, 2025
7b722f8
declare FEATURE_ASYNC_SAX_XML constant
mikeebowen Mar 17, 2025
8079fe5
Merge remote-tracking branch 'upstream/main' into add-async-xmlwriter…
mikeebowen Mar 17, 2025
095973f
add missing OpenXmlPartWriter constructors
mikeebowen Mar 17, 2025
aeb8c57
add abstract methods to OpenXmlWriter
mikeebowen Mar 17, 2025
b409310
reorder PublicAPI.Shipped.txt
mikeebowen Mar 17, 2025
46bada0
reorder methods
mikeebowen Mar 17, 2025
e3e30d3
reorder methods in OpenXmlPartWriter
mikeebowen Mar 17, 2025
bf67eb8
add unimplemented async methods
mikeebowen Mar 18, 2025
19eaef9
Add tests
mikeebowen Mar 18, 2025
2e0636f
add more tests
mikeebowen Mar 18, 2025
94ef2cc
rename tests
mikeebowen Mar 19, 2025
6e5e262
remove async methods that use an OpenXmlReader
mikeebowen Mar 19, 2025
b3ff0d6
add #if FEATURE_ASYNC_SAX_XML to PublicAPI.shipped for OpenXmlPartWri…
mikeebowen Mar 20, 2025
602ad1e
use version specific PublicAPI.shipped.txt files
mikeebowen Mar 20, 2025
83613f0
update PublishAPI.shipped files and change abstract methods to virtual
mikeebowen Mar 20, 2025
622c3f6
revert PublicAPI.Shipped.txt to match main
mikeebowen Mar 20, 2025
2875ad6
make tests use equal instead of contains
mikeebowen Mar 21, 2025
e07449c
use OpenXmlPartWriterSettings for async settings
mikeebowen Mar 24, 2025
5f1d125
add blank lines for readability
mikeebowen Mar 24, 2025
0b03d26
use async method
mikeebowen Mar 24, 2025
0421110
remove unnecessary async and add default sync implementations
mikeebowen Mar 25, 2025
6c3a0a0
Merge remote-tracking branch 'upstream/main' into add-async-xmlwriter…
mikeebowen Mar 25, 2025
e97b0b5
remove removed methods from PublicAPI.Shipped.txt
mikeebowen Mar 25, 2025
c2b9513
only hide Async in OpenXmlPartWriter constructors from older versions…
mikeebowen Mar 25, 2025
0ec2368
remove repeated calls to ThrowIfObjectDisposed
mikeebowen Mar 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<LatestTargetFramework>net8.0</LatestTargetFramework>
<SamplesFrameworks>net8.0</SamplesFrameworks>
<SamplesFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(SamplesFrameworks);net472</SamplesFrameworks>
<DefineConstants Condition=" '$(TargetFramework)' != 'net35' And '$(TargetFramework)' != 'net40' And '$(TargetFramework)' != 'net46' And '$(TargetFramework)' != 'net472' ">$(DefineConstants);FEATURE_ASYNC_SAX_XML</DefineConstants>
</PropertyGroup>
</Otherwise>
</Choose>
Expand Down
242 changes: 242 additions & 0 deletions src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
#if FEATURE_ASYNC_SAX_XML
using DocumentFormat.OpenXml.Framework;
using System.Threading.Tasks;
#endif
using System.Xml;

namespace DocumentFormat.OpenXml
Expand Down Expand Up @@ -56,6 +60,36 @@ public OpenXmlPartWriter(OpenXmlPart openXmlPart, Encoding encoding)
_xmlWriter = XmlWriter.Create(partStream, settings);
}

#if FEATURE_ASYNC_SAX_XML
/// <summary>
/// Initializes a new instance of the OpenXmlPartWriter.
/// </summary>
/// <param name="openXmlPart">The OpenXmlPart to be written to.</param>
/// <param name="settings">The settings for the OpenXmlPartWriter.</param>
public OpenXmlPartWriter(OpenXmlPart openXmlPart, OpenXmlPartWriterSettings settings)
{
if (openXmlPart is null)
{
throw new ArgumentNullException(nameof(openXmlPart));
}

if (settings is null)
{
throw new ArgumentNullException(nameof(settings));
}

var partStream = openXmlPart.GetStream(FileMode.Create);
XmlWriterSettings xmlWriterSettings = new()
{
CloseOutput = true,
Encoding = settings.Encoding,
Async = settings.Async,
};

_xmlWriter = XmlWriter.Create(partStream, xmlWriterSettings);
}
#endif

/// <summary>
/// Initializes a new instance of the OpenXmlPartWriter.
/// </summary>
Expand Down Expand Up @@ -91,6 +125,35 @@ public OpenXmlPartWriter(Stream partStream, Encoding encoding)
_xmlWriter = XmlWriter.Create(partStream, settings);
}

#if FEATURE_ASYNC_SAX_XML
/// <summary>
/// Initializes a new instance of the OpenXmlPartWriter.
/// </summary>
/// <param name="partStream">The given part stream.</param>
/// <param name="settings">The settings for the OpenXmlPartWriter.</param>
public OpenXmlPartWriter(Stream partStream, OpenXmlPartWriterSettings settings)
{
if (partStream is null)
{
throw new ArgumentNullException(nameof(partStream));
}

if (settings is null)
{
throw new ArgumentNullException(nameof(settings));
}

XmlWriterSettings xmlWriterSettings = new()
{
CloseOutput = settings.CloseOutput,
Encoding = settings.Encoding,
Async = settings.Async,
};

_xmlWriter = XmlWriter.Create(partStream, xmlWriterSettings);
}
#endif

#region public OpenXmlWriter methods

/// <summary>
Expand Down Expand Up @@ -365,5 +428,184 @@ public override void Close()
}

#endregion

// Async Methods
#if FEATURE_ASYNC_SAX_XML
/// <summary>
/// Asynchronously writes the XML declaration with the version "1.0".
/// </summary>
public override Task WriteStartDocumentAsync()
{
ThrowIfObjectDisposed();

return _xmlWriter.WriteStartDocumentAsync();
}

/// <summary>
/// Asynchronously writes the XML declaration with the version "1.0" and the standalone attribute.
/// </summary>
/// <param name="standalone">If true, it writes "standalone=yes"; if false, it writes "standalone=no". </param>
public override Task WriteStartDocumentAsync(bool standalone)
{
ThrowIfObjectDisposed();

return _xmlWriter.WriteStartDocumentAsync(standalone);
}

/// <summary>
/// Asynchronously writes out a start tag of the element and all the attributes of the element.
/// </summary>
/// <param name="elementObject">The OpenXmlElement object to be written.</param>
public async override Task WriteStartElementAsync(OpenXmlElement elementObject)
{
if (elementObject is null)
{
throw new ArgumentNullException(nameof(elementObject));
}

if (elementObject is OpenXmlMiscNode)
{
throw new ArgumentOutOfRangeException(nameof(elementObject));
}

ThrowIfObjectDisposed();

await _xmlWriter.WriteStartElementAsync(elementObject.Prefix, elementObject.LocalName, elementObject.NamespaceUri).ConfigureAwait(true);

if (elementObject.HasAttributes)
{
// write attributes
foreach (var attribute in elementObject.GetAttributes())
{
await _xmlWriter.WriteAttributeStringAsync(attribute.Prefix, attribute.LocalName, attribute.NamespaceUri, attribute.Value).ConfigureAwait(true);
}
}

if (elementObject is OpenXmlLeafTextElement)
{
_isLeafTextElementStart = true;
}
else
{
_isLeafTextElementStart = false;
}
}

/// <summary>
/// Asynchronously writes out a start tag of the element. And write the attributes in attributes. The attributes of the element will be omitted.
/// </summary>
/// <param name="elementObject">The OpenXmlElement object to be written.</param>
/// <param name="attributes">The attributes to be written.</param>
public override Task WriteStartElementAsync(OpenXmlElement elementObject, IEnumerable<OpenXmlAttribute> attributes)
{
if (elementObject is null)
{
throw new ArgumentNullException(nameof(elementObject));
}

return WriteStartElementAsync(elementObject, attributes, elementObject.NamespaceDeclarations);
}

/// <summary>
/// Asynchronously writes out a start tag of the element. And write the attributes in attributes. The attributes of the element will be omitted.
/// </summary>
/// <param name="elementObject">The OpenXmlElement object to be written.</param>
/// <param name="attributes">The attributes to be written.</param>
/// <param name="namespaceDeclarations">The namespace declarations to be written, can be null if no namespace declarations.</param>
public async override Task WriteStartElementAsync(OpenXmlElement elementObject, IEnumerable<OpenXmlAttribute> attributes, IEnumerable<KeyValuePair<string, string>> namespaceDeclarations)
{
if (elementObject is null)
{
throw new ArgumentNullException(nameof(elementObject));
}

if (elementObject is OpenXmlMiscNode)
{
throw new ArgumentOutOfRangeException(nameof(elementObject));
}

ThrowIfObjectDisposed();

await _xmlWriter.WriteStartElementAsync(elementObject.Prefix, elementObject.LocalName, elementObject.NamespaceUri).ConfigureAwait(true);

if (namespaceDeclarations is not null)
{
foreach (var item in namespaceDeclarations)
{
await _xmlWriter.WriteAttributeStringAsync(OpenXmlElementContext.XmlnsPrefix, item.Key, OpenXmlElementContext.XmlnsUri, item.Value).ConfigureAwait(true);
}
}

if (attributes is not null)
{
// write attributes
foreach (var attribute in attributes)
{
await _xmlWriter.WriteAttributeStringAsync(attribute.Prefix, attribute.LocalName, attribute.NamespaceUri, attribute.Value).ConfigureAwait(true);
}
}

if (elementObject is OpenXmlLeafTextElement)
{
_isLeafTextElementStart = true;
}
else
{
_isLeafTextElementStart = false;
}
}

/// <summary>
/// Asynchronously closes one element.
/// </summary>
public override Task WriteEndElementAsync()
{
ThrowIfObjectDisposed();

_isLeafTextElementStart = false;

return _xmlWriter.WriteEndElementAsync();
}

/// <summary>
/// Asynchronously writes the OpenXmlElement to the writer.
/// </summary>
/// <param name="elementObject">The OpenXmlElement object to be written.</param>
public async override Task WriteElementAsync(OpenXmlElement elementObject)
{
if (elementObject is null)
{
throw new ArgumentNullException(nameof(elementObject));
}

ThrowIfObjectDisposed();

await WriteStartElementAsync(elementObject).ConfigureAwait(true);

await WriteEndElementAsync().ConfigureAwait(true);

_isLeafTextElementStart = false;
}

/// <summary>
/// Asynchronously writes the given text content.
/// </summary>
/// <param name="text">The text to be written. </param>
public override Task WriteStringAsync(string text)
{
ThrowIfObjectDisposed();

if (_isLeafTextElementStart)
{
return _xmlWriter.WriteStringAsync(text);
}
else
{
throw new InvalidOperationException(ExceptionMessages.InvalidWriteStringCall);
}

// can continue WriteStringAsync(), so don't set _isLeafTextElementStart to false.
}
#endif
}
}
29 changes: 29 additions & 0 deletions src/DocumentFormat.OpenXml.Framework/OpenXmlPartWriterSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if FEATURE_ASYNC_SAX_XML
using System.Text;

namespace DocumentFormat.OpenXml;

/// <summary>
/// Settings for the OpenXmlPartWriter.
/// </summary>
public class OpenXmlPartWriterSettings
{
/// <summary>
/// Gets or sets a value indicating whether asynchronous OpenXmlPartWriter methods can be used.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Gets or sets a value indicating whether asynchronous OpenXmlPartWriter methods can be used.
/// Gets or sets a value indicating whether asynchronous <see cref="OpenXmlPartWriter" /> methods can be used.

/// </summary>
public bool Async { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the OpenXmlPartWriter should check to ensure that all characters in the document conform to the "2.2 Characters" section of the W3C XML 1.0 Recommendation.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Gets or sets a value indicating whether the OpenXmlPartWriter should check to ensure that all characters in the document conform to the "2.2 Characters" section of the W3C XML 1.0 Recommendation.
/// Gets or sets a value indicating whether the <see cref="OpenXmlPartWriter" /> should check to ensure that all characters in the document conform to the "2.2 Characters" section of the W3C XML 1.0 Recommendation.

Do you have a link to that part of the docs?

Copy link
Member

Choose a reason for hiding this comment

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

Is that really what the "CloseOutput" property does?

/// </summary>
public bool CloseOutput { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the OpenXmlPartWriter should also close the underlying stream or TextWriter when the Close() method is called.
Copy link
Member

Choose a reason for hiding this comment

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

I think these summaries are off...

/// </summary>
public Encoding Encoding { get; set; } = Encoding.UTF8;
}
#endif
Loading
Loading