【问题标题】:Generate word doc through OpenXML通过 OpenXML 生成 word doc
【发布时间】:2017-09-26 10:33:21
【问题描述】:

我有一个 ASP.NET 4.5 Web 表单应用程序,在 IIS 7.5 上运行。

我正在尝试从其中一个页面生成一个 word 文档,其中我有一个自定义表单。

我上传了一个包含合并字段的 word 文档模板。 在后面的代码中,我想根据 sql 数据库查询填充合并字段。

对于某些合并字段,我需要插入多行文本。其中一些甚至有项目符号列表。这些文本片段我无法存储在 sql 中,因此我将它们添加到带有书签的单独 word 文档中。

所以,回顾一下:

Template.dotx -> 包含合并字段

Data.docx -> 包含已用书签标记的文本片段。

我已设法使用 OpenXML 替换 Template.dotx 中的合并字段,但我找不到将数据从书签中获取到合并字段中的方法。

这对 Interop 很有效,但是当我将它上传到服务器时遇到了问题,所以我切换到了 OpenXML。

这是我迄今为止尝试过的:

private string GetBookmarkData(WordprocessingDocument secondWordDoc, string bookmarkKey)
    {
            string returnVal = "";
            foreach (BookmarkStart bookmarkStart in secondWordDoc.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
            {
                if(bookmarkStart.Name == bookmarkKey)
                {
                    foreach(Run run in bookmarkStart.Parent.Descendants<Run>())
                    {
                        returnVal += run.Descendants<Text>().FirstOrDefault().Text + "<br/>";
                    }
                }
            }
            return returnVal;
        }
    
    
    protected void PrintBtn_Click(object sender, EventArgs e)
    {
                string mainTemplate = Server.MapPath("~/MyFolder/Template.dotx");
                string savePath = Server.MapPath("~/SaveFolder/Final.docx");
    
                File.Copy(mainTemplate, savePath);
                using(WordprocessingDocument firstDoc = WordprocessingDocument.Open(savePath, true))
                {
                    using (WordprocessingDocument secondDoc = WordprocessingDocument.Open(Server.MapPath("~/MyFolder/Data.docx"), true))
                    {
                        foreach (FieldCode field in firstDoc.MainDocumentPart.RootElement.Descendants<FieldCode>())
                        {
                            var fieldNameStart = field.Text.LastIndexOf(" MERGEFIELD", System.StringComparison.Ordinal);
                            String fieldText = field.InnerText;
                            if (fieldText.StartsWith(" MERGEFIELD"))
                            {
                                Int32 endMerge = fieldText.IndexOf("\\");
                                Int32 fieldNameLength = fieldText.Length - endMerge;
                                String fieldName = fieldText.Substring(11, endMerge - 11);
                                fieldName = fieldName.Trim();
                                string autoFill = "";
    
                                    switch (fieldName)
                                    {
                                        case "MergeField1":
                                            autoFill = mergefield_1;
                                            break;
                                        case "MergeField2":
                                            autoFill = mergefield_2;
                                            break;
                                        case "MergeField3":
                                            autoFill = GetBookmarkData(secondDoc, "Bookmark1");
                                            break;
                                        case "MergeField4":
                                            autoFill = GetBookmarkData(secondDoc, "Bookmark2");
                                            break;
                                        case "MergeField5":
                                            autoFill = GetBookmarkData(secondDoc, "Bookmark3");
                                            break;
                                  }
                            }
    
                            foreach (Run run in firstDoc.MainDocumentPart.Document.Descendants<Run>())
                            {
                               foreach (Text txtFromRun in run.Descendants<Text>().Where(a => a.Text == "«" + fieldName + "»"))
                               {
                                  txtFromRun.Text = autoFill;
                               }
                            }
                        }
                    } 
                }
                            
        firstDoc.ChangeDocumentType(WordprocessingDocumentType.Document);
        firstDoc.MainDocumentPart.Document.Save();
    }
}

那么这有什么作用呢?

当我单击一个按钮时,我调用方法 PrintBtn_Click。在做了一些 SQL 魔术(我没有包含在其中)之后,我初始化了一些将填充每个合并字段的变量。这个例子是一个简短的编辑版本。原版要大很多。使用此代码,我设法填充了合并字段。它工作得很好。但是方法:`

string GetBookmarkData(WordprocessingDocument secondWordDoc, string bookmarkKey)`

并没有真正做到它应该做的事情。它应该进入 Data.docx,从我指定的书签中检索所有文本。它只返回没有项目符号或奇怪格式的行。

我在使用 Interop 时使用了相同的流程,没有任何问题。如何使用 OpenXML 做到这一点?带有项目符号的行是否存储在不同的 xml 中?

我试图检索 BookmarkStart 和 BookmarkEnd 之间的所有 Runs 并从中获取 Text。

更新

secondDoc 实际上是 Data.docx,看起来像这样:

Bookmark1

•   Text-Information 1 (This is just an example)
•   Text-Information 2 (This is just an example)
•   Text-Information 3 (This is just an example)
•   Text-Information 4 (This is just an example)

Bookmark2

This is a list of multiple items:
Item 1                              x.000,00 
Item 2                              x.000,00 
Item 3                              x.000,00 
Item 4                              x.000,00 
Item 5                              000,00 
This is the conclusion for this list.

Following is a list of other multiple items:
Item 1                              x.000,00 
Item 2                              x.000,00 
Item 3                              x.000,00 
Item 4                              x.000,00 
Item 5                              000,00 
This is the conclusions for this list


Bookmark3

a)  Another example of text that needs to go in the mergefield:
•   Article 1 xxxx  Quantity/Producer etc
•   Article 2 xxxx  Quantity/Producer etc
Some details about this block of text that is not relevant but I need to insert it in the merge field as well

因此,如果按下某个单选按钮,则“Bookmark1”/“Bookmark2”/“Bookmark3”之后的整个文本需要进入其特定的合并字段。我已经为这些文本块添加了书签。正如我在上面告诉你的,它只插入一些没有项目符号的行。例如,与 Bookmark2 对应的合并字段,仅接收“这是多个项目的列表:”。

【问题讨论】:

  • 为了更好地帮助您,我们需要查看 secondDoc。一旦我们可以查看结构,我们就可以帮助您对代码进行故障排除。
  • 我已经更新了最初的帖子。我希望这很清楚。谢谢!
  • 很清楚,但不够清楚。我们需要实际的文档来检查确切的 xml 结构。这将有助于确定您的方法未返回正确值的原因。如果您不想或不能共享文件,我理解。您需要做的是下载 OpenXML Productivity Tool (microsoft.com/en-us/download/details.aspx?id=30425) 并使用它打开文档以检查 xml 结构。然后根据结构重写你的 GetBookmarkData 方法。如果您发布文档,我们会为您完成 ;)
  • 文档没有其他内容。就像我在描述中发布的一样。我无法上传文件,因为它包含公司数据。我有 OpenXML Productivity Tool,但我不太了解它。我正在查看 /word/document.xml -> w:document -> w:body....有很多段落..所以我单击包含 BookmarkStart 的一个。在运行中,我可以看到第一行。在另一个参数之后是下一行的另一个运行,依此类推,直到 BookmarkEnd。子弹在哪里?在 /word/numbering.xml 中?它们有什么关系?
  • 这是我所拥有的一个确切示例:drive.google.com/open?id=0BwlWg0JEosuNUTNpdGI2QkwxNms 所以我需要从 SecondTemplate.docx 中获取项目符号列表,并将其放入 MainTemplate.docx 的合并字段中。我已将该列表添加为 Bookmark1。也许有比使用书签更好的方法来做到这一点?

标签: c# asp.net ms-word openxml


【解决方案1】:

查看您的文档和代码,我发现有两个地方可能是您的问题的根源:

First:包含Bookmark1 的 SecondTemplate.docx 的 xml 布局如下:

<Paragraph>
    <Bookmarkstart name=bookmark1/>
    <Run>
        <Text "Item 1">
    </Run>
</Paragraph>
<Paragraph>
    <Run>
        <Text "Item 2">
    </Run>
</Paragraph>    
<Paragraph>
    <Run>
        <Text "Item 3">
    </Run>
</Paragraph>    
<Paragraph>
    <Run>
        <Text "Item 4">
    </Run>
    <Bookmarkend/>
</Paragraph>    

你的代码在这里:

            if(bookmarkStart.Name == bookmarkKey)
            {
                foreach(Run run in bookmarkStart.Parent.Descendants<Run>())
                {
                    returnVal += run.Descendants<Text>().FirstOrDefault().Text + "<br/>";
                }
            }

bookmarkstart.Parent 调用运行时,它会匹配书签正上方的Paragraph

<Paragraph>
    <Bookmarkstart name=bookmark1/>
    <Run>
        <Text "Item 1">
    </Run>
</Paragraph>

因此,当循环的其余部分执行时,您只会将“第 1 项”拉入合并过程。您需要重新设计逻辑以正确匹配 BookmarkStart 和 BookmarkEnd 之间所有四个段落的运行中的文本。

第二:在 OpenXml 中经常绊倒人们的另一个问题是,当您尝试在此处匹配 Descendants 调用中的 Run 时:

 bookmarkStart.Parent.Descendants<Run>

如果您指的是 DocumentFormat.OpenXml.Drawing.Run,而不是正确的 'DocumentFormat.OpenXml.Wordprocessing.Run',这可能会阻止匹配 - 所以在 Visual Studio 中将鼠标悬停在 Run 上并确保您匹配正确的 Run .调整您的 using 语句以获得正确的语句。像

这样的 Using 语句
using Run = DocumentFormat.OpenXml.Wordprocessing.Run;

通常根据该文件中的其余代码使用。希望这些线索对你有所帮助。

【讨论】:

  • 这完全有道理!我太盲目了,我一开始没有看到这个。这就是为什么我只得到第一个项目。现在我有一个新方法,我正在从 sql 获取数据。我将该文本存储在一个字段中,并添加了一个分隔符,例如“|||”换新线。我使用正则表达式拆分字符串,并为 OpenXML 中的新行插入 Breaks。它非常难看,但它有效。我将重构我的代码,以返回为多行文本块使用 doc 模板。如果你们知道更好、更清洁的方法,请告诉我。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-17
  • 1970-01-01
  • 1970-01-01
  • 2020-11-26
  • 1970-01-01
  • 1970-01-01
  • 2010-12-01
相关资源
最近更新 更多