C# 使用OpenXMLSDK在Word文档中简单地替换令牌

C# 使用OpenXMLSDK在Word文档中简单地替换令牌,c#,openxml,C#,Openxml,我有一个要求,我希望用户在Word文档中键入一些字符串标记,以便可以通过C#应用程序用一些值替换它们。假设我有一个与图片一致的文档 现在使用SDK,我可以阅读以下文档: private void InternalParseTags(WordprocessingDocument aDocumentToManipulate) { StringBuilder sbDocumentText = new StringBuilder(); using (Stre

我有一个要求,我希望用户在Word文档中键入一些字符串标记,以便可以通过C#应用程序用一些值替换它们。假设我有一个与图片一致的文档

现在使用SDK,我可以阅读以下文档:

  private void InternalParseTags(WordprocessingDocument aDocumentToManipulate)
    {
        StringBuilder sbDocumentText = new StringBuilder();
        using (StreamReader sr = new StreamReader(aDocumentToManipulate.MainDocumentPart.GetStream()))
        {
            sbDocumentText.Append(sr.ReadToEnd());
        }
但是,由于这是原始XML,我无法轻松搜索标记,因为底层XML如下所示:

<w:t>&lt;:</w:t></w:r><w:r w:rsidR="002E53FF" w:rsidRPr="000A794A"><w:t>Person.Meta.Age
:Person.Meta.Age
(显然不是我能控制的)而不是我所希望的:

<w:t>&lt;: Person.Meta.Age
:Person.Meta.Age


这是OpenXML的一个棘手问题。我遇到的最佳解决方案如下所述:


基本上,Eric会扩展内容,使每个角色都独立运行,然后查找启动“的运行,而不是直接查找/替换令牌,使用OpenXML,您可以使用一些基于第三方OpenXML的模板,使用起来很简单,而且很快就能收回成本

正如Scanny指出的,OpenXML充满了令人讨厌的细节,人们必须逐一掌握这些细节。学习曲线又长又陡。如果你想成为OpenXML的大师,那么就去努力吧,开始攀登。如果您想有时间享受体面的社交生活,还有其他选择:只需选择一个基于OpenXML的第三方工具包。我已经评估过了。它提供了基于模板的方法,您可以准备一个模板,该模板是Word格式的文件,其中包含用于在运行时从应用程序合并的数据的占位符。它们都支持MS Word支持的任何格式,您可以使用条件内容、表格等

您还可以使用DOM方法创建或更改文档。最终文件可以是.docx或.pdf

Docentric是许可产品,但您很快就会通过使用这些工具节省的时间来补偿成本


如果要在服务器上运行应用程序,请不要使用interop-有关详细信息,请参阅此链接:()。

以下是我快速拼凑的一些代码,用于解释xml中跨运行的令牌。我对图书馆了解不多,但我能把它用起来。由于所有的循环,这也可能需要一些性能增强

/// <summary>
    /// Iterates through texts, concatenates them and looks for tokens to replace 
    /// </summary>
    /// <param name="texts"></param>
    /// <param name="tokenNameValuePairs"></param>
    /// <returns>T/F whether a token was replaced.  Should loop this call until it returns false.</returns>
    private bool IterateTextsAndTokenReplace(IEnumerable<Text> texts, IDictionary<string, object> tokenNameValuePairs)
    {
        List<Text> tokenRuns = new List<Text>();
        string runAggregate = String.Empty;
        bool replacedAToken = false;

        foreach (var run in texts)
        {
            if (run.Text.Contains(prefixTokenString) || runAggregate.Contains(prefixTokenString))
            {
                runAggregate += run.Text;
                tokenRuns.Add(run);

                if (run.Text.Contains(suffixTokenString))
                {
                    if (possibleTokenRegex.IsMatch(runAggregate))
                    {
                        string possibleToken = possibleTokenRegex.Match(runAggregate).Value;
                        string innerToken = possibleToken.Replace(prefixTokenString, String.Empty).Replace(suffixTokenString, String.Empty);
                        if (tokenNameValuePairs.ContainsKey(innerToken))
                        {
                            //found token!!!
                            string replacementText = runAggregate.Replace(prefixTokenString + innerToken + suffixTokenString, Convert.ToString(tokenNameValuePairs[innerToken]));
                            Text newRun = new Text(replacementText);
                            run.InsertAfterSelf(newRun);
                            foreach (Text runToDelete in tokenRuns)
                            {
                                runToDelete.Remove();
                            }
                            replacedAToken = true;
                        }
                    }
                    runAggregate = String.Empty;
                    tokenRuns.Clear();
                }
            }
        }

        return replacedAToken;
    }

以及调用函数的一些示例:

using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(memoryStream, true))
                        {
                            bool replacedAToken = true;

                            //continue to loop document until token's have not bee replaced.  This is because some tokens are spread across 'runs' and may need a second iteration of processing to catch them.
                            while (replacedAToken)
                            {
                                //get all the text elements
                                IEnumerable<Text> texts = wordDoc.MainDocumentPart.Document.Body.Descendants<Text>();
                                replacedAToken = this.IterateTextsAndTokenReplace(texts, tokenNameValuePairs);
                            }
                            wordDoc.MainDocumentPart.Document.Save();


                            foreach (FooterPart footerPart in wordDoc.MainDocumentPart.FooterParts)
                            {
                                if (footerPart != null)
                                {
                                    Footer footer = footerPart.Footer;

                                    if (footer != null)
                                    {
                                        replacedAToken = true;

                                        while (replacedAToken)
                                        {
                                            IEnumerable<Text> footerTexts = footer.Descendants<Text>();
                                            replacedAToken = this.IterateTextsAndTokenReplace(footerTexts, tokenNameValuePairs);
                                        }
                                        footer.Save();
                                    }
                                }
                            }

                            foreach (HeaderPart headerPart in wordDoc.MainDocumentPart.HeaderParts)
                            {
                                if (headerPart != null)
                                {
                                    Header header = headerPart.Header;

                                    if (header != null)
                                    {
                                        replacedAToken = true;

                                        while (replacedAToken)
                                        {
                                            IEnumerable<Text> headerTexts = header.Descendants<Text>();
                                            replacedAToken = this.IterateTextsAndTokenReplace(headerTexts, tokenNameValuePairs);
                                        }
                                        header.Save();
                                    }
                                }
                            }
                        }
使用(WordprocessingDocument wordDoc=WordprocessingDocument.Open(memoryStream,true))
{
bool replacedAToken=true;
//继续循环文档,直到未替换令牌。这是因为一些令牌分布在“运行”中,可能需要第二次处理迭代才能捕获它们。
while(replacedAToken)
{
//获取所有文本元素
IEnumerable text=wordDoc.MainDocumentPart.Document.Body.subjects();
replacedAToken=this.IterateTextsAndTokenReplace(文本,tokenNameValuePairs);
}
wordDoc.MainDocumentPart.Document.Save();
foreach(wordDoc.MainDocumentPart.FooterPart中的FooterPart FooterPart)
{
if(footerPart!=null)
{
Footer-Footer=footerPart.Footer;
如果(页脚!=null)
{
replacedAToken=true;
while(replacedAToken)
{
IEnumerable footerText=footer.subjects();
replacedAToken=this.IterateTextsAndTokenReplace(footerText,tokenNameValuePairs);
}
footer.Save();
}
}
}
foreach(HeaderPart HeaderPart在wordDoc.main DocumentPart.HeaderParts中)
{
if(headerPart!=null)
{
页眉页眉=页眉部分页眉;
if(标题!=null)
{
replacedAToken=true;
while(replacedAToken)
{
IEnumerable headerTexts=header.subjections();
replacedAToken=this.IterateTextsAndTokenReplace(headerText,tokenNameValuePairs);
}
header.Save();
}
}
}
}

类似的问题:您可以尝试Eric White的Openxml powertools。在这里检查我的答案-
/// <summary>
    /// Iterates through texts, concatenates them and looks for tokens to replace 
    /// </summary>
    /// <param name="texts"></param>
    /// <param name="tokenNameValuePairs"></param>
    /// <returns>T/F whether a token was replaced.  Should loop this call until it returns false.</returns>
    private bool IterateTextsAndTokenReplace(IEnumerable<Text> texts, IDictionary<string, object> tokenNameValuePairs)
    {
        List<Text> tokenRuns = new List<Text>();
        string runAggregate = String.Empty;
        bool replacedAToken = false;

        foreach (var run in texts)
        {
            if (run.Text.Contains(prefixTokenString) || runAggregate.Contains(prefixTokenString))
            {
                runAggregate += run.Text;
                tokenRuns.Add(run);

                if (run.Text.Contains(suffixTokenString))
                {
                    if (possibleTokenRegex.IsMatch(runAggregate))
                    {
                        string possibleToken = possibleTokenRegex.Match(runAggregate).Value;
                        string innerToken = possibleToken.Replace(prefixTokenString, String.Empty).Replace(suffixTokenString, String.Empty);
                        if (tokenNameValuePairs.ContainsKey(innerToken))
                        {
                            //found token!!!
                            string replacementText = runAggregate.Replace(prefixTokenString + innerToken + suffixTokenString, Convert.ToString(tokenNameValuePairs[innerToken]));
                            Text newRun = new Text(replacementText);
                            run.InsertAfterSelf(newRun);
                            foreach (Text runToDelete in tokenRuns)
                            {
                                runToDelete.Remove();
                            }
                            replacedAToken = true;
                        }
                    }
                    runAggregate = String.Empty;
                    tokenRuns.Clear();
                }
            }
        }

        return replacedAToken;
    }
string prefixTokenString = "{";
    string suffixTokenString = "}";

    Regex possibleTokenRegex = new Regex(prefixTokenString + "[a-zA-Z0-9-_]+" + suffixTokenString);
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(memoryStream, true))
                        {
                            bool replacedAToken = true;

                            //continue to loop document until token's have not bee replaced.  This is because some tokens are spread across 'runs' and may need a second iteration of processing to catch them.
                            while (replacedAToken)
                            {
                                //get all the text elements
                                IEnumerable<Text> texts = wordDoc.MainDocumentPart.Document.Body.Descendants<Text>();
                                replacedAToken = this.IterateTextsAndTokenReplace(texts, tokenNameValuePairs);
                            }
                            wordDoc.MainDocumentPart.Document.Save();


                            foreach (FooterPart footerPart in wordDoc.MainDocumentPart.FooterParts)
                            {
                                if (footerPart != null)
                                {
                                    Footer footer = footerPart.Footer;

                                    if (footer != null)
                                    {
                                        replacedAToken = true;

                                        while (replacedAToken)
                                        {
                                            IEnumerable<Text> footerTexts = footer.Descendants<Text>();
                                            replacedAToken = this.IterateTextsAndTokenReplace(footerTexts, tokenNameValuePairs);
                                        }
                                        footer.Save();
                                    }
                                }
                            }

                            foreach (HeaderPart headerPart in wordDoc.MainDocumentPart.HeaderParts)
                            {
                                if (headerPart != null)
                                {
                                    Header header = headerPart.Header;

                                    if (header != null)
                                    {
                                        replacedAToken = true;

                                        while (replacedAToken)
                                        {
                                            IEnumerable<Text> headerTexts = header.Descendants<Text>();
                                            replacedAToken = this.IterateTextsAndTokenReplace(headerTexts, tokenNameValuePairs);
                                        }
                                        header.Save();
                                    }
                                }
                            }
                        }