7

快被这个问题发疯了。我确信它是如此简单,我只是想念它,但我无法终生了解如何使用 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();
}

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

4

6 回答 6

7

我找到了一种更好的方法来使用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也是一个很好的参考。这一篇讨论向放置在富文本内容控件中的表添加行。

于 2010-01-18T17:50:05.590 回答
3

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

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

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

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

于 2010-01-20T18:31:21.107 回答
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

于 2010-01-22T01:53:46.137 回答
1

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

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();
}
于 2010-01-20T19:46:56.487 回答
1

另一种解决方案是

        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);
于 2011-03-06T12:41:42.340 回答
1

内容控制类型

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

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

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

令人困惑的是,在 XML 中,这两种类型都被标记为,<sdt>...</sdt>但底层的 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 文档来填充模板。

于 2021-02-12T18:45:08.160 回答