簡體   English   中英

使用OpenXML 2.5將數據寫入docx文檔中的TextInput元素

[英]Write data into TextInput elements in docx documents with OpenXML 2.5

我有一些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...
       }

我如何在每個TextInput寫入一個值?

謝謝!

首先,考慮市場上提供了簡單方法來設置表單域值的任何軟件產品(有些價格昂貴,但仍然值得)。

但是,如果有人堅持使用這里的OpenXML SDK是一種對我有用的方法(向下滾動以查看代碼)(根據我的經驗顯示了任務的復雜性,如果有人可以向我展示一種處理XML的OpenXML SDK方法,那將非常高興用它):

給定一個TextInput對象:

查找包含“單獨的” fieldchar的第一輪。 這將始終與textinput在同一段落中。

查找包含“ end” fieldchar的以下第一個運行。 這可能在同一段落中,但是如果表單域的現有值有任何段落,它將在另一段落中。

在包含“單獨” fieldchar的運行之后找到第一個運行。 如果此運行是包含“結束” fieldchar的運行,則進行新運行並將其添加到包含“單獨” fieldchar的運行之后。

刪除此運行中的所有文本元素(保留所有rPr)。

刪除以下所有運行,直到包含“ end” fieldchar的運行。
(還必須刪除任何段落,但包含“結束”字段字符的段落必須與包含“結束”字段字符的段落合並。)

現在可以設置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);
   }
}

注意1 :不能保證上述邏輯可以與textinput表單域的所有可能變體一起使用。 應當閱讀所有相關元素的開放xml文檔,以查看是否有任何哥特。 一件事是用戶在Word或任何其他編輯器中編輯的文檔。 另一件事是由處理OpenXML的許多軟件產品創建/編輯的文檔。

注意2 :在Word中簡單地制作一些文檔非常有幫助。
每個都包含一個textinput表單域,其中
- 沒有價值
-單行文字
-多行文字
-多段文字
-多個空段
-字體和段落格式(f.ex字體大小20,段落行距三聯)
然后在Visual Studio中打開這些文件,然后查看document.xml(使用“設置文檔格式”功能以獲取可讀的xml)。
這令人大開眼界,因為它揭示了表單域的復雜性,並可能導致人們重新考慮處理表單域的產品。

注意3 :在表單域類型和格式方面存在未解決的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM