【问题标题】:How do you change the content of a content control in Word 2007 with OpenXml SDK 2.0?如何使用 OpenXml SDK 2.0 在 Word 2007 中更改内容控件的内容?
【发布时间】:2010-01-15 23:51:25
【问题描述】:

快被这个问题发疯了。我确信它是如此简单,我只是想念它,但我终生无法找到如何使用 C# 中的 OpenXml SDK v2.0 更改 Word 2007 中内容控件的内容。

我创建了一个带有纯文本内容控件的 Word 文档。此控件的标记是“FirstName”。在代码中,我想打开 Word 文档,找到这个内容控件,并在不丢失格式的情况下更改内容。

我最终找到的解决方案是找到内容控件,在它之后插入一个运行,然后删除内容控件:

using (WordprocessingDocument wordProcessingDocument = WordprocessingDocument.Open(filePath, true)) {
MainDocumentPart mainDocumentPart = wordProcessingDocument.MainDocumentPart;
SdtRun sdtRun = mainDocumentPart.Document.Descendants<SdtRun>()
 .Where(run => run.SdtProperties.GetFirstChild<Tag>().Val == "FirstName").Single();

if (sdtRun != null) {
 sdtRun.Parent.InsertAfter(new Run(new Text("John")), sdtRun);
 sdtRun.Remove();
}

这确实会更改文本,但我会丢失所有格式。有谁知道我该怎么做?

【问题讨论】:

    标签: openxml word-2007


    【解决方案1】:

    我找到了一个更好的方法来使用http://wiki.threewill.com/display/enterprise/SharePoint+and+Open+XML#SharePointandOpenXML-UsingWord2007ContentControls 作为参考。您的结果可能会有所不同,但我认为这会让您有一个良好的开端:

    using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Open(filePath, true)) {
        var sdtRuns = mainDocumentPart.Document.Descendants<SdtRun>()
            .Where(run => run.SdtProperties.GetFirstChild<Tag>().Val.Value == contentControlTagValue);
    
        foreach (SdtRun sdtRun in sdtRuns) {
            sdtRun.Descendants<Text>().First().Text = replacementText;
        }
    
        wordprocessingDocument.MainDocumentPart.Document.Save();
    }
    

    我认为上述内容仅适用于纯文本内容控件。不幸的是,它并没有摆脱最终文档中的内容控制。如果我得到它的工作,我会发布它。

    http://msdn.microsoft.com/en-us/library/cc197932.aspx如果你想找一个富文本内容控件也是一个很好的参考。这篇文章讨论了向放置在富文本内容控件中的表格添加行。

    【讨论】:

    • 如果内容控件是段落中的唯一项目(即没有被其他文本包围),则此解决方案似乎不起作用。作为一种快速解决方法,我只是在内容控件的一侧放置了一个空格。当我找到一个更好的解决方案时,我会发布一个。
    【解决方案2】:

    您删除 sdtRun 并添加新的第一种方法显然会删除格式,因为您只添加了 Run 而不是 RunStyle。要保留格式,您应该创建运行元素,如

    new Run( new RunProperties(new RunStyle(){ Val = "MyStyle" }),
                                new Text("Replacement Text"));
    

    替换所有Decendants&lt;Text&gt; 的第二种方法仅适用于纯文本内容控件,因为富文本内容控件没有 SdtRun 元素。富文本内容控件是带有 SdtContent 元素的 SdtBlock。富文本内容控件可以有多个段落、多个运行和多个文本。因此,您的代码sdtRun.Descendants&lt;Text&gt;().First().Text = replacementText 对于富文本内容控件将存在缺陷。没有一行代码可以替换富内容控件的整个文本并保留所有格式。

    我不明白您所说的“它没有摆脱最终文档中的内容控制”是什么意思?我认为您的要求是仅通过保留内容控件和格式来更改文本(内容)。

    【讨论】:

      【解决方案3】:

      解决如何达到预期结果的一个极好的方法是使用 Open XML SDK 2.0 附带的文档反射器工具......

      例如,您可以:

      1. 在文档中每个内容控件的“属性”对话框中,选中“编辑内容时删除内容控件”。
      2. 填写并保存为新文档。
      3. 使用反射器比较原始版本和保存版本。
      4. 点击显示/隐藏代码按钮,它将显示将原始版本转换为填充版本所需的代码。

      它并不完美,但非常有用。您也可以直接查看任一文档的标记,并查看填充控件引起的更改。

      这是一种有点脆弱的方法,因为 Wordprocessing ML 可能很复杂;很容易搞砸。对于简单的文本控件,我只使用这种方法:

      private void FillSimpleTextCC(SdtRun simpleTextCC, string replacementText)
          {
              // remove the showing place holder element      
              SdtProperties ccProperties = simpleTextCC.SdtProperties;
              ccProperties.RemoveAllChildren<ShowingPlaceholder>();
      
              // fetch content block Run element            
              SdtContentRun contentRun = simpleTextCC.SdtContentRun;
              var ccRun = contentRun.GetFirstChild<Run>();
      
              // if there was no placeholder text in the content control, then the SdtContentRun
              // block will be empty -> ccRun will be null, so create a new instance
              if (ccRun == null)
              {
                  ccRun = new Run(
                      new RunProperties() { RunStyle = null },
                      new Text());
                  contentRun.Append(ccRun);
              }
      
              // remove revision identifier & replace text
              ccRun.RsidRunProperties = null;
              ccRun.GetFirstChild<Text>().Text = replacementText;
      
              // set the run style to that stored in the SdtProperties block, if there was
              // one. Otherwise the existing style will be used.            
              var props = ccProperties.GetFirstChild<RunProperties>();
              if (props != null)
              if (props != null)
              {
                  RunStyle runStyle = props.RunStyle;
                  if (runStyle != null)
                  {
                      // set the run style to the same as content block property style.
                      var runProps = ccRun.RunProperties;
                      runProps.RunStyle = new RunStyle() { Val = runStyle.Val };
                      runProps.RunFonts = null;
                  }
              }
          }
      

      希望在某种程度上有所帮助。 :D

      【讨论】:

      • 这几天搞定了,但我会尽快重新审视并研究上述建议。谢谢
      【解决方案4】:

      我还必须查找和替换页脚中的文本。您可以使用以下代码找到它们:

      using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Open(file.PhysicalFile.FullName, true)) {
          foreach (FooterPart footerPart in wordprocessingDocument.MainDocumentPart.FooterParts) {
              var footerPartSdtRuns = footerPart.Footer.Descendants<SdtRun>()
                  .Where(run => run.SdtProperties.GetFirstChild<Tag>().Val.Value == contentControlTag);
      
              foreach (SdtRun sdtRun in footerPartSdtRuns) {
                 sdtRun.Descendants<Text>().First().Text = replacementTerm;
              }
          }
      
          wordprocessingDocument.MainDocumentPart.Document.Save();
      }
      

      【讨论】:

        【解决方案5】:

        另一种解决方案是

                SdtRun rOld = p.Elements<SdtRun>().First();
        
                string OldNodeXML = rOld.OuterXml;
                string NewNodeXML = OldNodeXML.Replace("SearchString", "ReplacementString");
        
                SdtRun rNew = new SdtRun(NewNodeXML);
        
        
                p.ReplaceChild<SdtRun>(rNew, rOld);
        

        【讨论】:

          【解决方案6】:

          内容控制类型

          根据 Word 文档中的插入点,创建了两种类型的内容控件:

          • 顶级(与段落处于同一级别)

          • 嵌套(通常在现有段落中)

          令人困惑的是,在 XML 中,这两种类型都被标记为 &lt;sdt&gt;...&lt;/sdt&gt;,但底层的 openXML 类是不同的。 对于顶级,根是SdtBlock,内容是SdtContentBlock。对于嵌套,它是SdtRun & SdtContentRun

          要获得这两种类型,即所有内容控件,最好通过SdtElement 的公共基类进行迭代,然后检查类型:

          List<SdtElement> sdtList = document.Descendants<SdtElement>().ToList();
          
          foreach( SdtElement sdt in sdtList )
          {
             if( sdt is SdtRun )
             {
                ; // process nested sdts
             }
          
             if( sdt is SdtBlock )
             {
                ; // process top-level sdts
             }
          }
          

          对于一个文档模板,所有的内容控件都应该被处理——一个以上的内容控件具有相同的标签名称是很常见的,例如客户名称,所有这些通常都需要替换为实际的客户名字。

          内容控制标签名称

          内容控制标签名永远不会被拆分。

          在 XML 中,这是:

          <w:sdt>
          ...
          <w:sdtPr>
          ...
          <w:tag w:val="customer-name"/>
          

          因为标签名从不拆分,所以总是可以直接匹配找到:

             List<SdtElement> sdtList = document.Descendants<SdtElement>().ToList();
                  
             foreach( SdtElement sdt in sdtList )
             {
                 if( sdt is SdtRun )
                 {
                   String tagName = sdt.SdtProperties.GetFirstChild<Tag>().Val;
          
                   if( tagName == "customer-name" )
                   {
                      ; // get & replace placeholder with actual value
                   }
          

          显然,在上面的代码中,需要一种更优雅的机制来检索对应于每个不同标记名的实际值。

          内容控制文本

          在内容控件中,渲染文本被拆分为多个运行是很常见的(尽管每个运行具有相同的属性)。

          除其他外,这是由拼写/语法检查器和编辑尝试次数引起的。 当使用分隔符时,文本拆分更常见,例如 [customer-name] 等。

          这很重要的原因是,如果不检查 XML,就无法保证占位符文本没有被拆分,因此无法找到和替换。

          一种建议的方法

          一种建议的方法是仅使用纯文本内容控件、顶级和/或嵌套,然后:

          • 通过标签名查找内容控件

          • 插入格式化段落或在内容控件之后运行

          • 删除内容控件

             List<SdtElement> sdtList = document.Descendants<SdtElement>().ToList();
            
             foreach( SdtElement sdt in sdtList )
             {
                if( sdt is SdtRun )
                {
                   String tagName = sdt.SdtProperties.GetFirstChild<Tag>().Val;
            
                   String newText = "new text"; // eg GetTextByTag( tagName );
            
                   // should use a style or common run props
            
                   RunProperties runProps = new RunProperties();
            
                   runProps.Color    = new Color   () { Val   = "000000" };
                   runProps.FontSize = new FontSize() { Val   = "23" };
                   runProps.RunFonts = new RunFonts() { Ascii = "Calibri" };
            
                   Run run = new Run();
            
                   run.Append( runProps );
                   run.Append( new Text( newText ) );
            
                   sdt.InsertAfterSelf( run );
            
                   sdt.Remove();
                }
            
                if( sdt is SdtBlock )
                {
                   ; // add paragraph
                }
             }
            

          对于顶级类型,需要插入一个段落。

          在这种方法中,内容控件仅用作可以保证找到的占位符(通过标记名),然后完全替换为适当的文本(格式一致)。

          此外,这消除了格式化内容控制文本的需要(然后可能会被拆分,因此无法找到。)

          为标记名称使用合适的命名约定,例如 Xpath 表达式,可以实现更多可能性,例如使用其他 XML 文档来填充模板。

          【讨论】:

            猜你喜欢
            • 2010-12-23
            • 1970-01-01
            • 1970-01-01
            • 2014-11-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-09-16
            相关资源
            最近更新 更多