即将发生这个问题。我确信它很简单,我只是错过了它,但我不能为我的生活找到如何使用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();
}
这确实会改变文字,但我丢失了所有格式。有谁知道我怎么能这样做?
答案 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附带的文档反射器工具....
例如,您可以:
它并不完美,但却非常有用。您也可以直接查看任一文档的标记,并查看填充控件所引起的更改。
这是一种有点脆弱的方法,因为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 文档来填充模板。