从 XElement 转换后无效的 OpenXml

Invalid OpenXml after converting from XElement

我正在使用 this code 从 XElement 转换为 OpenXmlElement

    internal static OpenXmlElement ToOpenXml(this XElement xel)
    {
        using (var sw = new StreamWriter(new MemoryStream()))
        {
            sw.Write(xel.ToString());
            sw.Flush();
            sw.BaseStream.Seek(0, SeekOrigin.Begin);

            var re = OpenXmlReader.Create(sw.BaseStream);
            re.Read();

            var oxe = re.LoadCurrentElement();
            re.Close();
            return oxe;
        }
    }

转换前我有一个 XElement

    <w:ind w:firstLine="0" w:left="0" w:right="0"/>    

转换后是这样的

    <w:ind w:firstLine="0" w:end="0" w:start="0"/>

此元素随后使用以下方法未能通过 OpenXml 验证

    var v = new OpenXmlValidator();
    var errs = v.Validate(doc);

报告错误:

    Description="The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:start' attribute is not declared."
    Description="The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:end' attribute is not declared."

我是否需要执行其他操作才能将这些属性添加到架构中,或者我是否需要找到一种从 XElement 转换为 OpenXml 的新方法?

我正在使用 nuget 包 DocumentFormat.OpenXml ver 2.9.1(最新)。

编辑:查看 the OpenXml standard,似乎 left/start 和 right/end 都应该被识别,这表明 OpenXmlValidator 不太正确。大概我可以忽略那些验证错误吧?

非常感谢

简短的回答是您确实可以忽略那些特定的验证错误。 OpenXmlValidator 在这种情况下不是最新的。

我会另外提供更优雅的 ToOpenXml 方法实现(请注意 using 声明,这是在 C# 8.0 中添加的)。

internal static OpenXmlElement ToOpenXmlElement(this XElement element)
{
    // Write XElement to MemoryStream.
    using var stream = new MemoryStream();
    element.Save(stream);
    stream.Seek(0, SeekOrigin.Begin);

    // Read OpenXmlElement from MemoryStream.
    using OpenXmlReader reader = OpenXmlReader.Create(stream);
    reader.Read();
    return reader.LoadCurrentElement();
}

如果您不使用 C# 8.0 或 using 声明,这里是带有 using 语句的相应代码。

internal static OpenXmlElement ToOpenXmlElement(this XElement element)
{
    using (var stream = new MemoryStream())
    {
        // Write XElement to MemoryStream.
        element.Save(stream);
        stream.Seek(0, SeekOrigin.Begin);

        // Read OpenXmlElement from MemoryStream.
        using OpenXmlReader reader = OpenXmlReader.Create(stream);
        {
            reader.Read();
            return reader.LoadCurrentElement();
        }
    }
}

这是相应的单元测试,它还表明您必须传递一个 w:document 才能让 w:ind 元素的属性由在此过程中创建的 Indentation 实例更改.

public class OpenXmlReaderTests
{
    private const string NamespaceUriW = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
    private static readonly string XmlnsW = $"xmlns:w=\"{NamespaceUriW}\"";

    private static readonly string IndText =
        $@"<w:ind {XmlnsW} w:firstLine=""10"" w:left=""20"" w:right=""30""/>";

    private static readonly string DocumentText =
        $@"<w:document {XmlnsW}><w:body><w:p><w:pPr>{IndText}</w:pPr></w:p></w:body></w:document>";

    [Fact]
    public void ConvertingDocumentChangesIndProperties()
    {
        XElement element = XElement.Parse(DocumentText);

        var document = (Document) element.ToOpenXmlElement();
        Indentation ind = document.Descendants<Indentation>().First();

        Assert.Null(ind.Left);
        Assert.Null(ind.Right);

        Assert.Equal("10", ind.FirstLine);
        Assert.Equal("20", ind.Start);
        Assert.Equal("30", ind.End);
    }

    [Fact]
    public void ConvertingIndDoesNotChangeIndProperties()
    {
        XElement element = XElement.Parse(IndText);

        var ind = (OpenXmlUnknownElement) element.ToOpenXmlElement();

        Assert.Equal("10", ind.GetAttribute("firstLine", NamespaceUriW).Value);
        Assert.Equal("20", ind.GetAttribute("left", NamespaceUriW).Value);
        Assert.Equal("30", ind.GetAttribute("right", NamespaceUriW).Value);
    }
}