dotnet / dotnet/Open-XML-SDK

w:sdtContent-related classes should inherit from SdtContentElement or similar

Open
#944 4 comments 0 reactions 0 assignees View on GitHub
api-design-needed help wanted request: feature
Dominant language
C#
Stars
4.6k
Forks
605
PR merge metrics
No merged PRs in 30d

Description

**Description**

In a code example produced for the DevDays Redmond 2021, I wanted to show how developers can use the strongly-typed classes to remove (potentially nested) `w:sdt` and related elements (e.g., `w:sdtContent`) from the markup. The good news is that `SdtBlock`, `SdtRun`, `SdtRow`, and `SdtCell` are all derived from `SdtElement`, meaning that all kinds of `w:sdt` elements can be matched by `SdtElement`. However, the same is unfortunately not true for `SdtContentBlock`, `SdtContentRun`, `SdtContentRow`, and `SdtContentCell`. They are directly derived from `OpenXmlCompositeElement` instead of a (non-existent) base class like `SdtContentElement` that represents any `w:sdtContent` element. Therefore, we can't match `w:sdtContent` elements using just one class. We must use the four concrete strongly typed classes to catch all potential `w:sdtContent` elements.

Therefore, while the `SdtElement` class would be enough to match `w:sdt` elements, there is no `SdtContent` property of type `SdtContentElement` that would let us match the `w:sdtContent` child elements. Thus, we have to match all kinds of `w:sdt` elements individually and specifically.

While this is not the end of the world, the strongly typed classes could provide an easier and more straightforward way to do such things.

The Open XML SDK also provides other ways to achieve this, e.g., by getting elements by their `LocalName` (e.g., `"sdtContent"`) or even `XName` (e.g., `W.sdtContent`). But that means we don't use the strongly typed classes. Instead, we use `OpenXmlElement` and some very generic ways to deal with XML markup, much as with Linq to XML.

**Information**

- .NET Target: Any recent target
- DocumentFormat.OpenXml Version: Any version

**Repro**

Here is the code example that shows how the strongly typed classes are used to match `w:sdt` and `w:sdtContent` elements.

```csharp
private static object RemoveSdtsTransform(OpenXmlElement element)
{
return element switch
{
SdtBlock sdt => sdt.SdtContentBlock.Elements().AggregateResultsOf(RemoveSdtsTransform),
SdtRun sdt => sdt.SdtContentRun.Elements().AggregateResultsOf(RemoveSdtsTransform),
SdtRow sdt => sdt.SdtContentRow.Elements().AggregateResultsOf(RemoveSdtsTransform),
SdtCell sdt => sdt.SdtContentCell.Elements().AggregateResultsOf(RemoveSdtsTransform),

_ => element.TransformAggregating(RemoveSdtsTransform)
};
}
```

Here is the corresponding Linq to XML example, in which `w:sdt` and `w:sdtContent` elements are matched in a straightforward fashion:

```csharp
private static object RemoveSdtsTransform(XNode node)
{
return node switch
{
XElement element when element.Name == W.sdt =>
element.Element(W.sdtContent)?.Nodes().Select(RemoveSdtsTransform),

XElement element =>
new XElement(element.Name,
element.Attributes(),
element.Nodes().Select(RemoveSdtsTransform)),
_ => node
};
}

```

**Observed**

The strongly typed classes don't make it as easy as it could be.

**Expected**

The strongly typed classes should make it as easy as with Linq to XML. For example, it would be nice if we could write something like the following in case we simply want to catch all `w:sdt` and `w:sdtContent` elements.

```csharp
private static object RemoveSdtsTransform(OpenXmlElement element)
{
return element switch
{
SdtElement sdt => sdt.SdtContent.Elements().AggregateResultsOf(RemoveSdtsTransform),
_ => element.TransformAggregating(RemoveSdtsTransform)
};
}
```

Where we want to only match specific kinds of `w:sdt` elements (e.g., just block-level), we can still use the derived classes (e.g., `SdtBlock`).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.