w:sdtContent-related classes should inherit from SdtContentElement or similar
#944 创建于 2021年6月12日
描述
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.
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:
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.
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).