如何使用OpenXml SDK 2.0更改Word 2007中的内容控件的内容?

时间:2010-01-15 23:51:25

标签: openxml word-2007

即将发生这个问题。我确信它很简单,我只是错过了它,但我不能为我的生活找到如何使用C#中的OpenXml SDK v2.0更改Word 2007中内容控件的内容。

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

我最终开始工作的解决方案包括找到内容控件,在其后面插入一个run,然后删除内容控件:

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();
}

这确实会改变文字,但我丢失了所有格式。有谁知道我怎么能这样做?

6 个答案:

答案 0 :(得分: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也是一个很好的参考。本文讨论了将行添加到放置在富文本内容控件中的表。

答案 1 :(得分: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将存在缺陷。没有一行代码可以替换丰富内容控件的整个文本,但保留所有格式。

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

答案 2 :(得分:3)

一种解决如何实现所需结果的优秀方法是使用Open XML SDK 2.0附带的文档反射器工具....

例如,您可以:

  1. 在文档中每个内容控件的“属性”对话框中,选中“编辑内容时删除内容控件”。
  2. 填写并将其保存为新文档。
  3. 使用反射器比较原始版本和保存的版本。
  4. 点击显示/隐藏代码按钮,它会显示将原始内容转换为填写版本所需的代码。
  5. 它并不完美,但却非常有用。您也可以直接查看任一文档的标记,并查看填充控件所引起的更改。

    这是一种有点脆弱的方法,因为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

答案 3 :(得分: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();
}

答案 4 :(得分: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);

答案 5 :(得分:0)

内容控制类型

根据 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 文档来填充模板。