如何将内容放在docx的合并域中

时间:2011-12-06 12:50:34

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

我正在使用asp.net开发一个Web应用程序,我有一个名为Template.docx的文件,它像模板一样生成其他报告。在这个Template.docx中,我有一些MergeFields(Title,CustomerName,Content,Footer等)来代替C#中的一些动态内容。

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

我不知道MergeFields是否是正确的方法,或者是否有另一种方法。如果你能建议我,我很感激!

PS:我的网络应用程序中引用了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转换为文件时,我收到了一个损坏的文件。谢谢!

3 个答案:

答案 0 :(得分:5)

我知道这是一个老帖子,但我无法得到为我工作的公认答案。链接的项目甚至不会编译(有人已在该链接中评论过)。此外,它似乎使用其他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是一个简单的静态类,我曾用它来帮助我替换合并字段。

答案 1 :(得分:2)

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

答案 2 :(得分:2)

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

对于SimpleFields,文档中显示给用户的文本运行是SimpleField的子项。

对于FieldCodes,显示给用户的文本运行在包含具有Separate和End FieldCharValues的FieldChars的运行之间。有时,在Separate和End Elements之间存在几个包含运行的文本。

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

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;
}