Jon*_*ero 1 c# docx openxml openxml-sdk
我有一些docx文件。我使用OpenXML 2.5 SDK阅读它们,并TextInput在每个文档中搜索。
byte[] filebytes = System.IO.File.ReadAllBytes("Test.docx");
using (MemoryStream stream = new MemoryStream(filebytes))
using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
{
IEnumerable<FormFieldData> fields = wordDocument.MainDocumentPart.Document.Descendants<FormFieldData>();
foreach (var field in fields)
{
IEnumerable<TextInput> textInputs = field.Descendants<TextInput>();
foreach (var ti in textInputs)
{
<<HERE>>
}
}
wordDocument.MainDocumentPart.Document.Save();
stream.Flush();
ETC...
}
Run Code Online (Sandbox Code Playgroud)
我如何在每个值中写入一个值TextInput?
谢谢!
首先,考虑市场上提供了简单方法来设置表单域值的任何软件产品(有些价格昂贵,但仍然值得)。
但是,如果有人坚持使用这里的OpenXML SDK是一种对我有用的方法(向下滚动以查看代码)(根据我的经验显示了任务的复杂性,如果有人可以向我展示可以处理该问题的OpenXML SDK方法,那将非常高兴用它):
给定一个TextInput对象:
查找包含“单独的” fieldchar的第一轮。这将始终与textinput在同一段落中。
查找包含“ end” fieldchar的以下第一个运行。这可能在同一段落中,但是如果表单域的现有值有任何段落,它将在另一段落中。
在包含“单独” fieldchar的运行之后找到第一个运行。如果此运行是包含“结束” fieldchar的运行,则进行新运行并将其添加到包含“单独” fieldchar的运行之后。
删除此运行中的所有文本元素(保留所有rPr)。
删除以下所有运行,直到包含“ end” fieldchar的运行。
(还必须删除任何段落,但包含“ end”字段字符的段落必须与包含“ end”字段字符的段落合并。)
现在可以设置formfield的值。
如果该值中的任何行均用作段落,则使用包含“单独的”字段字符的段落的深层克隆来制作一个段落“模板”。
从段落模板中删除除pPr之外的所有内容。
对于该值的第一行,只需在单个运行中添加一个文本元素,我们现在就可以在包含“单独” fieldchar的运行与包含“ end” fieldchar的运行之间。
每增加一行:
如果该行不打算用作段落:
稍加休息一下(<br/>)。
深度克隆之前的运行并设置text元素,然后添加它。
如果该行打算用作段落:
深度克隆段落模板,并将其添加到保存上一次运行的段落之后。
深度克隆之前的运行并设置text元素,然后添加它。
如果添加了任何段落,请将包含“ 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<Run>().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<FieldChar>().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<FieldChar>().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<Run>().FirstOrDefault(ru =>
ru.Elements<FieldChar>().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<Run>().FirstOrDefault(ru =>
ru.Elements<FieldChar>().FirstOrDefault(fc =>
fc.FieldCharType == FieldCharValues.End)
!= null);
}
// Remove any existing value.
Run rFirst = rSeparate.NextSibling<Run>();
if (rFirst == null || rFirst == rEnd)
{
RunProperties rPr = rTextInput.GetFirstChild<RunProperties>();
if (rPr != null) rPr = rPr.CloneNode(true) as RunProperties;
rFirst = rSeparate.InsertAfterSelf<Run>(new Run(new[] { rPr }));
}
rFirst.RemoveAllChildren<Text>();
Run r = rFirst.NextSibling<Run>();
while(r != rEnd)
{
if (r != null)
{
r.Remove();
r = rFirst.NextSibling<Run>();
}
else // next paragraph
{
Paragraph p = rFirst.Parent.NextSibling<Paragraph>();
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<Run>();
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<Text>(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<Break>(new Break());
line = lines[i];
text = rFirst.AppendChild<Text>(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<Run>(r.CloneNode(true) as Run);
}
}
Run Code Online (Sandbox Code Playgroud)
注意1:不能保证上述逻辑可以与textinput表单域的所有可能变体一起使用。应当阅读所有相关元素的开放xml文档,以查看是否有任何哥特。一件事是用户在Word或任何其他编辑器中编辑的文档。另一件事是由处理OpenXML的许多软件产品创建/编辑的文档。
注意2:在Word中简单地制作一些文档非常有帮助。
每个字段都包含一个单一的textinput格式字段,该字段具有
-无值
-一行文本
-多个文本行-多个文本
段落
-多个空段落
-字体和段落格式(例如,字体大小为20,段落行距为trippel),
然后打开这些在Visual Studio中,查看document.xml(使用“格式文档”功能获取可读的xml)。
这令人大开眼界,因为它揭示了表单域的复杂性,并可能导致人们重新考虑处理表单域的产品。
注意3:在表单域类型和格式方面存在未解决的问题。