【问题标题】:how can I put a content in a mergefield in docx如何将内容放在 docx 的合并字段中
【发布时间】:2016-10-09 09:37:00
【问题描述】:

我正在使用 asp.net 开发一个 Web 应用程序,并且我有一个名为 Template.docx 的文件,它的作用类似于生成其他报告的模板。在这个 Template.docx 中,我有一些 MergeFields(标题、客户名称、内容、页脚等)来替换 C# 中的一些动态内容。

我想知道,如何将内容放在 docx 的合并字段中?

我不知道 MergeFields 是否是正确的方法,或者是否有其他方法。如果你能推荐我,我很感激!

PS:我的 web 应用程序中引用了 openxml。

编辑:

private MemoryStream LoadFileIntoStream(string fileName)
{
    MemoryStream memoryStream = new MemoryStream();
    using (FileStream fileStream = File.OpenRead(fileName))
    {
        memoryStream.SetLength(fileStream.Length);
        fileStream.Read(memoryStream.GetBuffer(), 0, (int) fileStream.Length);

        memoryStream.Flush();
        fileStream.Close();
    }
    return memoryStream;
}

public MemoryStream GenerateWord()
{
    string templateDoc = "C:\\temp\\template.docx"; 
    string reportFileName = "C:\\temp\\result.docx";

    var reportStream = LoadFileIntoStream(templateDoc);

    // Copy a new file name from template file
    //File.Copy(templateDoc, reportFileName, true);

    // Open the new Package
    Package pkg = Package.Open(reportStream, FileMode.Open, FileAccess.ReadWrite);

    // Specify the URI of the part to be read
    Uri uri = new Uri("/word/document.xml", UriKind.Relative);
    PackagePart part = pkg.GetPart(uri);

    XmlDocument xmlMainXMLDoc = new XmlDocument();
    xmlMainXMLDoc.Load(part.GetStream(FileMode.Open, FileAccess.Read));

    // replace some keys inside xml (it will come from database, it's just a test)
    xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_customer", "My Customer Name");
    xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_title", "Report of Documents");
    xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_content", "Content of Document");

    // Open the stream to write document
    StreamWriter partWrt = new StreamWriter(part.GetStream(FileMode.Open, FileAccess.Write));
    //doc.Save(partWrt);
    xmlMainXMLDoc.Save(partWrt);

    partWrt.Flush();
    partWrt.Close();
    reportStream.Flush();
    pkg.Close();

    return reportStream;
}

PS:当我将 MemoryStream 转换为文件时,我得到了一个损坏的文件。谢谢!

【问题讨论】:

  • 我认为这里不能使用 Word 来执行邮件合并?
  • 不,实际上,我需要填写这些字段,我想将其替换为我的动态内容。我不知道 MergeFields 是否是正确的方法。谢谢!
  • 通常将内容放入 MergeField 的方式是与包含动态内容的数据源进行邮件合并。您使用什么 libaray 从 asp.net 代码中操作 docx 文件?
  • 嗯,我的项目中引用了DocumentFormat.OpenXml,但是没有必要使用。我找到了一个可以满足我需要的代码,并且可以正常工作:) ...但它使用 FileStream,所以,我想知道是否有任何方法可以在 MemoryStream 中生成 docx 并将其发送到客户端?看看我的编辑!谢谢@user957902
  • 看看 DocumentFormat.OpenXml.Wordprocessing.MailMerge 类。您也可以考虑改用 DocumentVariables,尽管这在某种程度上已被弃用。

标签: c# asp.net .net openxml docx


【解决方案1】:

我知道这是一篇旧帖子,但我无法获得为我工作的公认答案。链接的项目甚至不会编译(有人已经在该链接中评论过)。此外,它似乎使用其他 Nuget 包,如 WPFToolkit。

所以我在这里添加我的答案,以防有人发现它有用。这仅使用 OpenXML SDK 2.5 和 WindowsBase v4。这适用于 MS Word 2010 及更高版本。

string sourceFile = @"C:\Template.docx";
string targetFile = @"C:\Result.docx";
File.Copy(sourceFile, targetFile, true);
using (WordprocessingDocument document = WordprocessingDocument.Open(targetFile, true))
{
    // If your sourceFile is a different type (e.g., .DOTX), you will need to change the target type like so:
    document.ChangeDocumentType(WordprocessingDocumentType.Document);

    // Get the MainPart of the document
    MainDocumentPart mainPart = document.MainDocumentPart;
    var mergeFields = mainPart.RootElement.Descendants<FieldCode>();

    var mergeFieldName = "SenderFullName";
    var replacementText = "John Smith";

    ReplaceMergeFieldWithText(mergeFields, mergeFieldName, replacementText);                   

    // Save the document
    mainPart.Document.Save();

}


private void ReplaceMergeFieldWithText(IEnumerable<FieldCode> fields, string mergeFieldName, string replacementText)
{
    var field = fields
        .Where(f => f.InnerText.Contains(mergeFieldName))
        .FirstOrDefault();

    if (field != null)
    {
        // Get the Run that contains our FieldCode
        // Then get the parent container of this Run
        Run rFldCode = (Run)field.Parent; 

        // Get the three (3) other Runs that make up our merge field
        Run rBegin = rFldCode.PreviousSibling<Run>();
        Run rSep = rFldCode.NextSibling<Run>();
        Run rText = rSep.NextSibling<Run>();
        Run rEnd = rText.NextSibling<Run>();

        // Get the Run that holds the Text element for our merge field
        // Get the Text element and replace the text content 
        Text t = rText.GetFirstChild<Text>();
        t.Text = replacementText;

        // Remove all the four (4) Runs for our merge field
        rFldCode.Remove();
        rBegin.Remove();
        rSep.Remove();
        rEnd.Remove();
    }

}

上面的代码基本上是这样的:

  • 确定构成名为“SenderFullName”的合并字段的 4 个运行。
  • 为我们的合并字段确定包含 Text 元素的 Run。
  • 删除 4 次运行。
  • 为我们的合并字段更新 Text 元素的 text 属性。

更新

对于任何感兴趣的人,here 是一个简单的静态类,用于帮助我替换合并字段。

【讨论】:

  • 非常感谢@Frank Fajardo。您希望进行的微小更改 - 1) 永远不会使用 var 容器,并且可以从代码中删除声明。 2) rText 的声明应该在 rSep 和 rEnd 之间(否则使用未声明的变量)。
  • @Brent,你是对的。我确实复制并粘贴并进行了修改以使其清楚,但没有正确执行。谢谢!
  • @Brent,我添加了一个指向我最终使用的代码的链接,以防你感兴趣。
  • 非常感谢 Frank - 不知道为什么我花了这么长时间才找到这个示例,但它非常适合理解 OpenXML 合并字段
  • 这样的代码忽略了FieldCode在一行中的第二个(可以是一个段落)。我不知道为什么。你能帮帮我吗?
【解决方案2】:

Frank Fajardo 的回答对我来说是 99%,但重要的是要注意 MERGEFIELDS 可以是 SimpleFields 或 FieldCodes。

对于 SimpleFields,在文档中显示给用户的文本是 SimpleField 的子级。

在 FieldCodes 的情况下,显示给用户的文本运行在包含 FieldChars 的运行之间,其中包含单独的 FieldChars 和 End FieldCharValues。有时,分隔元素和结束元素之间存在多个包含运行的文本。

下面的代码处理这些问题。有关如何从文档中获取所有 MERGEFIELDS 的更多详细信息,包括页眉和页脚,请参见 GitHub 存储库https://github.com/mcshaz/SimPlanner/blob/master/SP.DTOs/Utilities/OpenXmlExtensions.cs

private static Run CreateSimpleTextRun(string text)
{
    Run returnVar = new Run();
    RunProperties runProp = new RunProperties();
    runProp.Append(new NoProof());
    returnVar.Append(runProp);
    returnVar.Append(new Text() { Text = text });
    return returnVar;
}

private static void InsertMergeFieldText(OpenXmlElement field, string replacementText)
{
    var sf = field as SimpleField;
    if (sf != null)
    {
        var textChildren = sf.Descendants<Text>();
        textChildren.First().Text = replacementText;
        foreach (var others in textChildren.Skip(1))
        {
            others.Remove();
        }
    }
    else
    {
        var runs = GetAssociatedRuns((FieldCode)field);
        var rEnd = runs[runs.Count - 1];
        foreach (var r in runs
            .SkipWhile(r => !r.ContainsCharType(FieldCharValues.Separate))
            .Skip(1)
            .TakeWhile(r=>r!= rEnd))
        {
            r.Remove();
        }
        rEnd.InsertBeforeSelf(CreateSimpleTextRun(replacementText));
    }
}

private static IList<Run> GetAssociatedRuns(FieldCode fieldCode)
{
    Run rFieldCode = (Run)fieldCode.Parent;
    Run rBegin = rFieldCode.PreviousSibling<Run>();
    Run rCurrent = rFieldCode.NextSibling<Run>();

    var runs = new List<Run>(new[] { rBegin, rCurrent });

    while (!rCurrent.ContainsCharType(FieldCharValues.End))
    {
        rCurrent = rCurrent.NextSibling<Run>();
        runs.Add(rCurrent);
    };

    return runs;
}

private static bool ContainsCharType(this Run run, FieldCharValues fieldCharType)
{
    var fc = run.GetFirstChild<FieldChar>();
    return fc == null
        ? false
        : fc.FieldCharType.Value == fieldCharType;
}

【讨论】:

    【解决方案3】:

    您可以尝试http://www.codeproject.com/KB/office/Fill_Mergefields.aspx,它使用 Open XML SDK 来执行此操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-02
      • 1970-01-01
      • 1970-01-01
      • 2017-03-28
      • 2012-07-26
      • 1970-01-01
      相关资源
      最近更新 更多