2011-12-06 54 views
3

我正在用asp.net開發一個web應用程序,我有一個名爲Template.docx的文件,它像一個模板一樣工作來生成其他報告。在這個Template.docx裏面,我有一些MergeFields(標題,客戶名稱,內容,頁腳等)替換爲C#中的一些動態內容。我怎樣才能把內容放在docx中的mergefield上

我想知道,如何將內容放入docx中的mergefield中?

我不知道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到一個文件中,我有一個損壞的文件。謝謝!

+0

我假設是使用Word藉此在郵件合併是不是一個不錯的選擇? – user957902

+0

不,實際上,我需要填寫這些字段,我想將其替換爲我的動態內容。我不知道MergeFields是否是正確的方法。謝謝! –

+0

通常情況下,您將內容放入MergeField的方式是通過將郵件合併與包含動態內容的數據源進行合併。你使用什麼libaray從asp.net代碼中操作docx文件? – user957902

回答

1

我知道這是一箇舊帖子,但我無法得到接受的答案爲我工作。鏈接的項目甚至不會編譯(某人已經在該鏈接中發表評論)。此外,它似乎使用WPFToolkit等其他Nuget軟件包。

所以我在這裏添加我的答案,以防有人發現它有用。這隻使用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(); 
    } 

} 

什麼上面的代碼所做的基本上是這樣的:

  • 確定的4次運行,使一個名爲「SenderFullName」合併域。
  • 確定包含我們合併字段的文本元素的運行。
  • 刪除4次運行。
  • 更新我們合併字段的文本元素的文本屬性。

UPDATE

任何有興趣,here是我以前幫我更換合併域一個簡單的靜態類。

+0

非常感謝@Frank Fajardo。你喜歡做的小改動 - 1)從不使用var容器,可以從代碼中刪除聲明。 2)rText的聲明應該在rSep和rEnd之間(否則使用未聲明的變量)。 – Brent

+0

@Brent,你是對的。我做了複製和粘貼,並修改了一下,讓它清楚,但沒有正確地做。謝謝! –

+0

@Brent,我添加了一個鏈接到我最終使用的代碼,萬一你有興趣。 –

1

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

對於SimpleFields,向文檔中的用戶顯示的文本是SimpleField的子項。

對於FieldCodes,顯示給用戶的文本運行在包含FieldChars的運行與Separate和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; 
} 
相關問題