Skip to content

Implement OpenXmlElementFunctionalExtensions #679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Version 2.11.0
### Added
- Added `OpenXmlElementFunctionalExtensions.With` extension methods, which offer flexible means for constructing `OpenXmlElement` instances in the context of pure functional transformations.

## Version 2.10.0
### Added
- Added initial Office 2016 support, including `FileFormatVersion.Office2016`, `ExtendedChartPart` and other new schema elements (#586)
Expand Down
94 changes: 94 additions & 0 deletions src/DocumentFormat.OpenXml/OpenXmlElementFunctionalExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections;

namespace DocumentFormat.OpenXml
{
/// <summary>
/// Provides extension methods for pure functional transformations.
/// </summary>
public static class OpenXmlElementFunctionalExtensions
{
/// <summary>
/// Adds child elements or attributes to the given element.
/// </summary>
/// <typeparam name="TElement">The element type.</typeparam>
/// <param name="element">The element.</param>
/// <param name="content">The content to be added to the element.</param>
/// <returns>The element with the content added to it.</returns>
public static TElement With<TElement>(this TElement element, params object[] content)
where TElement : OpenXmlElement
{
return element.With((object) content);
}

/// <summary>
/// Adds child elements or attributes to the given element.
/// </summary>
/// <typeparam name="TElement">The element type.</typeparam>
/// <param name="element">The element.</param>
/// <param name="content">The content to be added to the element.</param>
/// <returns>The element with the content added to it.</returns>
public static TElement With<TElement>(this TElement element, object content)
where TElement : OpenXmlElement
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}

AddContent(element, content);
return element;
}

private static void AddContent(OpenXmlElement element, object content)
{
switch (content)
{
case null:
{
return;
}

case OpenXmlElement child:
{
if (child.Parent != null)
{
child = child.CloneNode(true);
}

element.AppendChild(child);
break;
}

case OpenXmlAttribute attribute:
{
element.SetAttribute(attribute);
break;
}

case object[] array:
{
foreach (object o in array)
{
AddContent(element, o);
}

break;
}

case IEnumerable enumerable:
{
foreach (object o in enumerable)
{
AddContent(element, o);
}

break;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// 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.Wordprocessing;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace DocumentFormat.OpenXml.Tests
{
public class OpenXmlElementFunctionalExtensionsTests
{
private const string NamespaceUriW14 = "http://schemas.microsoft.com/office/word/2010/wordml";

private static object[] GetElementArray()
{
return new object[]
{
new Run(new Text("A")),
new Run(new Text("B")),
};
}

private static List<object> GetElementList()
{
return new List<object>
{
new Run(new Text("C")),
new Run(new Text("D")),
};
}

private static List<object> GetNestedElementList()
{
return new List<object>
{
GetElementArray(),
GetElementList(),
};
}

private static List<OpenXmlAttribute> GetParagraphAttributes()
{
return new List<OpenXmlAttribute>
{
GetParagraphId(),
GetTextId(),
};
}

private static OpenXmlAttribute GetParagraphId()
{
return new OpenXmlAttribute("w14", "paraId", NamespaceUriW14, "3BA40FC3");
}

private static OpenXmlAttribute GetTextId()
{
return new OpenXmlAttribute("w14", "textId", NamespaceUriW14, "2E22A1BE");
}

[Fact]
public void With_AttributeList_Success()
{
List<OpenXmlAttribute> attributeList = GetParagraphAttributes();

Paragraph paragraph = new Paragraph().With(attributeList);

Assert.Equal(attributeList[0].Value, paragraph.ParagraphId);
Assert.Equal(attributeList[1].Value, paragraph.TextId);
Assert.Empty(paragraph.Elements<Run>());
}

[Fact]
public void With_Attribute_Success()
{
OpenXmlAttribute attribute = GetParagraphId();

Paragraph paragraph = new Paragraph().With(attribute);

Assert.Equal(attribute.Value, paragraph.ParagraphId);
Assert.Empty(paragraph.Elements<Run>());
}

[Fact]
public void With_ElementArray_Success()
{
object[] elementArray = GetElementArray();

Paragraph paragraph = new Paragraph().With(elementArray);

Assert.Equal("AB", paragraph.InnerText);
Assert.Equal(2, paragraph.Elements<Run>().Count());
}

[Fact]
public void With_ElementList_Success()
{
List<object> elementList = GetElementList();

Paragraph paragraph = new Paragraph().With(elementList);

Assert.Equal("CD", paragraph.InnerText);
Assert.Equal(2, paragraph.Elements<Run>().Count());
}

[Fact]
public void With_ElementToBeCloned_Success()
{
// Arrange, creating an element that is attached to a parent element.
var element = new Run(new Text("A"));
var parent = new Paragraph(element);

Assert.Equal("A", parent.InnerText);

// Act, creating a Paragraph with the above element, which needs to
// be cloned.
Paragraph paragraph = new Paragraph().With(element);

// Assert, demonstrating that the run was cloned and added to our
// newly created paragraph.
Assert.Equal("A", paragraph.InnerText);
Assert.Single(paragraph.Elements<Run>());
}

[Fact]
public void With_Element_Success()
{
var element = new Run(new Text("A"));

Paragraph paragraph = new Paragraph().With(element);

Assert.Equal("A", paragraph.InnerText);
Assert.Single(paragraph.Elements<Run>());
}

[Fact]
public void With_Enumerable_Success()
{
IEnumerable<object> enumerable = GetElementArray().Concat(GetElementList());

Paragraph paragraph = new Paragraph().With(enumerable);

Assert.Equal("ABCD", paragraph.InnerText);
Assert.Equal(4, paragraph.Elements<Run>().Count());
}

[Fact]
public void With_NestedElementList_Success()
{
List<object> nestedElementList = GetNestedElementList();

Paragraph paragraph = new Paragraph().With(nestedElementList);

Assert.Equal("ABCD", paragraph.InnerText);
Assert.Equal(4, paragraph.Elements<Run>().Count());
}

[Fact]
public void With_NullContent_NothingAdded()
{
Paragraph paragraph = new Paragraph().With(null);

Assert.Equal(string.Empty, paragraph.InnerText);
Assert.Empty(paragraph.Elements<Run>());
}

[Fact]
public void With_NullOpenXmlElement_ArgumentNullExceptionThrown()
{
Paragraph paragraph = null;

Assert.Throws<ArgumentNullException>(() => paragraph.With(new Run()));
}

[Fact]
public void With_WildMix_Success()
{
List<OpenXmlAttribute> attributeList = GetParagraphAttributes();

// Create paragraph with a w14:paraId attribute and 11 w:r elements.
Paragraph paragraph = new Paragraph().With(
attributeList,
new Run(new Text("1:")), // 1 w:r
GetElementArray(), // 2 w:r
new Run(new Text(",2:")), // 1 w:r
GetElementList(), // 2 w:r
new Run(new Text(",3:")), // 1 w:r
GetNestedElementList()); // 4 w:r

Assert.Equal(attributeList[0].Value, paragraph.ParagraphId);
Assert.Equal(attributeList[1].Value, paragraph.TextId);

Assert.Equal("1:AB,2:CD,3:ABCD", paragraph.InnerText);
Assert.Equal(11, paragraph.Elements<Run>().Count());
}
}
}