通过 OpenXml 编辑 MailMerge

Edit MailMerge via OpenXml

我有一个 .docx,其中包含一个 MailMerge,我需要通过代码 (C#) 更改它的源代码。作为图书馆,我正在使用 OpenXml。通过将 docx 作为 zip 文件打开并查找路径,我发现数据源的 link 存储在两个不同的位置:word/settings.xmlword/_rels/settings.xml.rels

要替换第一个:

var template = new MemoryStream(myDocxInByteArray);

using (var doc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Open(template, true))
{
    var s = doc.MainDocumentPart.DocumentSettingsPart.Settings;
    foreach (var els in s.ChildElements)
    {
        if (els is DocumentFormat.OpenXml.Wordprocessing.MailMerge)
        {
            var mm = (DocumentFormat.OpenXml.Wordprocessing.MailMerge)els;

            mm.Query.Val = "SELECT * FROM " + myNewPath;

            break;
        }
    }
}

这工作正常,但是当文件打开时,Word 要求从新路径和旧路径加载文件,因为它是在 word/_rels/settings.xml.rels 中定义的。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/mailMergeSource" Target="oldPath" TargetMode="External"/>
</Relationships>

如何在 WordprocessingDocument 中找到这个对象 (Relation)?找不到... 或者有其他方法可以改变它吗?

编辑:

我用不同的来源创建了两个不同的空文件,然后用 OpenXml Productivity Tool 打开以比较它们。正如我已经看到的,区别在于 word/settings.xmlword/_rels/settings.xml.rels.

我相信你可以这样做,如果你已经在设置部分的相关部分XML中有合适的rID(关系ID)。我给出的方法依赖于Linq。

但要注意Word在Settings.xml中可以存储两个个数据源文件名,在settings.xml.rels中有2个对应关系。更重要的是,它们都具有相同的 Relationship 类型字符串。 AIUI Word 在MailMerge 设置的ODSO ("Office Data Source Object") 部分实际上并没有使用数据文件名,但你应该检查一下。

获得旧关系的一种方法如下(这需要 Linq,就目前而言):

// Get the Settings part
var settingsPart = wordDocument.MainDocumentPart.DocumentSettingsPart;

// Create a MailMerge object and populate it with the existing data 
// (You do not have to do this - you could get the individual 
// elements/attributes from the part's XML)

var mailMerge = 
  new MailMerge(settingsPart.Settings.ChildElements.First<MailMerge>().OuterXml);

// Get the relationship

ReferenceRelationship oldRel 
  = settingsPart.ExternalRelationships.Where(
      Rel => Rel.Id == mailMerge.DataSourceReference.Id
    ).First();

/*
// to get the ODSO relationship, use the following, and to replace it, follow the pattern I give below for oldRel and newRel
ReferenceRelationship oldOdsoRel = 
  settingsPart.ExternalRelationships.Where(
    Rel => Rel.Id == mailMerge.DataSourceObject.SourceReference.Id
  ).First();
*/

// Delete the old Relationship (but notice that the oldRel object remains)

settingsPart.DeleteExternalRelationship(oldRel.Id);
var newRel = 
  settingsPart.AddExternalRelationship(
    oldRel.RelationshipType, 
    new Uri("your file path/name",UriKind.RelativeOrAbsolute),
    oldRel.Id);

最后我想到了以下解决方案:

using (WordprocessingDocument doc = WordprocessingDocument.Open(docInByteArray, isEditable: true))
{
    Settings settings = doc.MainDocumentPart.DocumentSettingsPart.Settings;
    OpenXmlElement openXmlElement = null;
    foreach (OpenXmlElement element in settings.ChildElements)
    {
        if (element is MailMerge mailMerge)
        {
            mailMerge.Query.Val = "SELECT * FROM " + csvPath;
            mailMerge.DataSourceObject.Remove();
        }
        else if (element is AttachedTemplate)
        {
            openXmlElement = element;
        }
    }
    if (openXmlElement != null)
    {
        openXmlElement.Remove();
    }
    doc.Save();
}