使用OpenXML 2.5将数据写入docx文档中的TextInput元素

我有一些docx文件。 我用OpenXML 2.5 SDK阅读它们,并在每个doc中搜索TextInput

  byte[] filebytes = System.IO.File.ReadAllBytes("Test.docx"); using (MemoryStream stream = new MemoryStream(filebytes)) using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true)) { IEnumerable fields = wordDocument.MainDocumentPart.Document.Descendants(); foreach (var field in fields) { IEnumerable textInputs = field.Descendants(); foreach (var ti in textInputs) { <> } } wordDocument.MainDocumentPart.Document.Save(); stream.Flush(); ETC... } 

我怎么能在每个TextInput写一个值?

谢谢!

首先考虑市场上的任何软件产品(一些相当昂贵,但仍然可能是值得的),它们提供了设置formfields值的简单方法。

但是,如果一个人坚持使用OpenXML SDK是一种适合我的方法(向下滚动查看代码)(在我体验它时显示任务的复杂性,如果有人可以向我展示OpenXML SDK方法,那将非常高兴用它):

给定TextInput对象:

找到包含“separate”fieldchar的第一个运行。 这将始终与textinput在同一段落中。

找到包含“end”fieldchar的第一个以下运行。 这可能在同一段中,但如果表格字段的现有值有任何段落,则它将在另一段中。

查找包含“separate”fieldchar的运行后的第一次运行。 如果此运行是包含“end”fieldchar的那个运行,则进行新的运行并在包含“separate”fieldchar的运行之后添加它。

删除此运行中的所有文本元素(保留任何rPr)。

删除所有以下运行,直到包含“end”fieldchar的运行。
(除了包含“end”fieldchar的段落之外,还必须删除任何段落,该段落必须与包含“end”fieldchar的那个合并。)

现在可以设置表单域的值。

如果值中的任何行用作段落,则使用包含“separate”fieldchar的段落的深度克隆来创建段落“模板”。
从段落模板中删除除pPr之外的所有内容。

对于值中的第一行,只需将一个文本元素添加到我们现在在包含“separate”fieldchar的运行和包含“end”fieldchar的运行之间的单次运行中。

每增加一行:

如果该行不是一个段落:

添加rest(
)。
深度克隆上一次运行并设置文本元素,然后添加它。

如果该行旨在成为段落:

深度克隆段落模板并在包含上一次运行的段落之后添加它。
深度克隆上一次运行并设置文本元素,然后添加它。

如果添加了任何段落,则将包含“end”fieldchar的运行和属于formfield的bookmarkend元素移动到最后一段添加的末尾。

执行上述但不支持输入值的段落:

 private static void SetFormFieldValue(TextInput textInput, string value) { // Code for http://stackoverflow.com/a/40081925/3103123 if (value == null) // Reset formfield using default if set. { if (textInput.DefaultTextBoxFormFieldString != null && textInput.DefaultTextBoxFormFieldString.Val.HasValue) value = textInput.DefaultTextBoxFormFieldString.Val.Value; } // Enforce max length. short maxLength = 0; // Unlimited if (textInput.MaxLength != null && textInput.MaxLength.Val.HasValue) maxLength = textInput.MaxLength.Val.Value; if (value != null && maxLength > 0 && value.Length > maxLength) value = value.Substring(0, maxLength); // Not enforcing TextBoxFormFieldType (read documentation...). // Just note that the Word instance may modify the value of a formfield when user leave it based on TextBoxFormFieldType and Format. // A curious example: // Type Number, format "# ##0,00". // Set value to "2016 was the warmest year ever, at least since 1999.". // Open the document and select the field then tab out of it. // Value now is "2 016 tht,tt" (the logic behind this escapes me). // Format value. (Only able to handle formfields with textboxformfieldtype regular.) if (textInput.TextBoxFormFieldType != null && textInput.TextBoxFormFieldType.Val.HasValue && textInput.TextBoxFormFieldType.Val.Value != TextBoxFormFieldValues.Regular) throw new ApplicationException("SetFormField: Unsupported textboxformfieldtype, only regular is handled.\r\n" + textInput.Parent.OuterXml); if (!string.IsNullOrWhiteSpace(value) && textInput.Format != null && textInput.Format.Val.HasValue) { switch (textInput.Format.Val.Value) { case "Uppercase": value = value.ToUpperInvariant(); break; case "Lowercase": value = value.ToLowerInvariant(); break; case "First capital": value = value[0].ToString().ToUpperInvariant() + value.Substring(1); break; case "Title case": value = System.Globalization.CultureInfo.InvariantCulture.TextInfo.ToTitleCase(value); break; default: // ignoring any other values (not supposed to be any) break; } } // Find run containing "separate" fieldchar. Run rTextInput = textInput.Ancestors().FirstOrDefault(); if (rTextInput == null) throw new ApplicationException("SetFormField: Did not find run containing textinput.\r\n" + textInput.Parent.OuterXml); Run rSeparate = rTextInput.ElementsAfter().FirstOrDefault(ru => ru.GetType() == typeof(Run) && ru.Elements().FirstOrDefault(fc => fc.FieldCharType == FieldCharValues.Separate) != null) as Run; if (rSeparate == null) throw new ApplicationException("SetFormField: Did not find run containing separate.\r\n" + textInput.Parent.OuterXml); // Find run containg "end" fieldchar. Run rEnd = rTextInput.ElementsAfter().FirstOrDefault(ru => ru.GetType() == typeof(Run) && ru.Elements().FirstOrDefault(fc => fc.FieldCharType == FieldCharValues.End) != null) as Run; if (rEnd == null) // Formfield value contains paragraph(s) { Paragraph p = rSeparate.Parent as Paragraph; Paragraph pEnd = p.ElementsAfter().FirstOrDefault(pa => pa.GetType() == typeof(Paragraph) && pa.Elements().FirstOrDefault(ru => ru.Elements().FirstOrDefault(fc => fc.FieldCharType == FieldCharValues.End) != null) != null) as Paragraph; if (pEnd == null) throw new ApplicationException("SetFormField: Did not find paragraph containing end.\r\n" + textInput.Parent.OuterXml); rEnd = pEnd.Elements().FirstOrDefault(ru => ru.Elements().FirstOrDefault(fc => fc.FieldCharType == FieldCharValues.End) != null); } // Remove any existing value. Run rFirst = rSeparate.NextSibling(); if (rFirst == null || rFirst == rEnd) { RunProperties rPr = rTextInput.GetFirstChild(); if (rPr != null) rPr = rPr.CloneNode(true) as RunProperties; rFirst = rSeparate.InsertAfterSelf(new Run(new[] { rPr })); } rFirst.RemoveAllChildren(); Run r = rFirst.NextSibling(); while(r != rEnd) { if (r != null) { r.Remove(); r = rFirst.NextSibling(); } else // next paragraph { Paragraph p = rFirst.Parent.NextSibling(); if (p == null) throw new ApplicationException("SetFormField: Did not find next paragraph prior to or containing end.\r\n" + textInput.Parent.OuterXml); r = p.GetFirstChild(); if (r == null) { // No runs left in paragraph, move other content to end of paragraph containing "separate" fieldchar. p.Remove(); while (p.FirstChild != null) { OpenXmlElement oxe = p.FirstChild; oxe.Remove(); if (oxe.GetType() == typeof(ParagraphProperties)) continue; rSeparate.Parent.AppendChild(oxe); } } } } if (rEnd.Parent != rSeparate.Parent) { // Merge paragraph containing "end" fieldchar with paragraph containing "separate" fieldchar. Paragraph p = rEnd.Parent as Paragraph; p.Remove(); while (p.FirstChild != null) { OpenXmlElement oxe = p.FirstChild; oxe.Remove(); if (oxe.GetType() == typeof(ParagraphProperties)) continue; rSeparate.Parent.AppendChild(oxe); } } // Set new value. if (value != null) { // Word API use \v internally for newline and \r for para. We treat \v, \r\n, and \n as newline (Break). string[] lines = value.Replace("\r\n", "\n").Split(new char[]{'\v', '\n', '\r'}); string line = lines[0]; Text text = rFirst.AppendChild(new Text(line)); if (line.StartsWith(" ") || line.EndsWith(" ")) text.SetAttribute(new OpenXmlAttribute("xml:space", null, "preserve")); for (int i = 1; i < lines.Length; i++) { rFirst.AppendChild(new Break()); line = lines[i]; text = rFirst.AppendChild(new Text(lines[i])); if (line.StartsWith(" ") || line.EndsWith(" ")) text.SetAttribute(new OpenXmlAttribute("xml:space", null, "preserve")); } } else { // An empty formfield of type textinput got char 8194 times 5 or maxlength if maxlength is in the range 1 to 4. short length = maxLength; if (length == 0 || length > 5) length = 5; rFirst.AppendChild(new Text(((char)8194).ToString())); r = rFirst; for (int i = 1; i < length; i++) r = r.InsertAfterSelf(r.CloneNode(true) as Run); } } 

注1 :上述逻辑不能保证与textinput表单字段的所有可能变体一起使用。 应该阅读所有相关元素的开放xml文档,看看是否有任何gothcas。 一件事是用户在Word或任何其他编辑器中编辑的文档。 另一件事是由处理OpenXML的任何数量的软件产品创建/编辑的文档。

注意2 :在Word中简单地制作一些文档非常有用。
每个包含一个textinput表单域
– 没有价值
– 单行文字
– 多行文字
– 多段文字
– 多个空段落
– 字体和段落格式(f.ex字体大小20,段落linespacing trippel)
然后在Visual Studio中打开它们并查看document.xml(使用Format documentfunction获取可读的xml)。
这令人大开眼界,因为它揭示了forms领域的复杂性,并可能导致人们通过处理它的产品重新考虑。

注3 :有关formfield类型和格式的未解决的问题。