我有一份受保护的文件(doc.ProtectionType == wdAllowOnlyFormFields).它有一些可以编辑的区域.其他一切都受到保护,甚至不会被复制.我正在使用NetOffice.Word库,并try 以编程方式查找文本并在Found范围内创建一个书签.问题是,当我try 调用方法wordDoc.Content.Duplicate.Find.Execute(smthParams)时,发生了异常"COMException: This method or property is not available because the object refers to a protected area of the document.".而且,我可以手动获取任何范围的文本,没有任何问题:

var range = doc.Content.Duplicate;
range.SetRange(start, end);

在以这种方式获得的范围内,我可以毫无问题地创建一个书签.但我无法通过这种方式找到与我要查找的文本对应的范围.我正在try 以这种方式创建书签:

public void CreateBookmarkTest()
{
    Document doc = Context.WordDocument;

    var searchText = "smth text";
    var bookmarkName = "newBookmark";
    
    using Range docRange = doc.Content.Duplicate;

    foreach (var paragraph in docRange.Paragraphs)
    {
        using Range paragraphRange = paragraph.Range;
        var text = paragraphRange.Text;
        var startParagraph = paragraphRange.Start;
        var endParagraph = paragraphRange.End;

        var startIndex = text.IndexOf(searchText);
        if (startIndex >= 0)
        {
            text = GetParagraphTextWithHiddenSymbols(paragraphRange, text);
            startIndex = text.IndexOf(searchText);
            var startFoundRange = startParagraph + startIndex;
            var end = startFoundRange + searchText.Length;

            paragraphRange.SetRange(startFoundRange, end);

            var foundText = paragraphRange.Text;
            if (foundText == searchText)
            {
                doc.Bookmarks.Add(bookmarkName, paragraphRange);
                break;
            }
        }
    }
}

private string GetParagraphTextWithHiddenSymbols(Range paragraphRange, string initialText)
{
    var text = initialText;
    foreach (Field field in paragraphRange.Fields)
    {
        int index = text.IndexOf(field.Result.Text);
        if (index >= 0)
        {
            text = text.Replace(field.Result.Text, $"{{{field.Code.Text}}} {field.Result.Text}{(char)21}");
        }
    }
    return text;
}

问题是,在这种情况下,并不总是foundText == searchText.有时foundText会被抵消,而我还想不出如何修复它.在我看来,这种方式既慢又不理想.也许有一些方法可以正确地实现搜索和文本替换(从Find.ExecuteFind.Execute是最理想的).我还在想,有没有什么方法可以让这些区域允许编辑(或者只知道当前区域是否允许编辑)?

我试着从下面的答案中使用奥斯卡的 idea 进行转换.代码的效果要好得多,但它也会在包含大量不受保护的输入字段的大段落上出错.

非常感谢你的帮助,朋友!

推荐答案

应该是其中的hidden text like Field's code text个导致了这个问题.NetOfficeMicrosoft.Office.Interop.WordVBA等. 你可以先试试我的代码.尽管到目前为止这还不是一个完美的解决方案,但请注意以下块代码片段:

if (range.Text != searchText)
                {

                    Console.WriteLine(range.Text);
                    System.Diagnostics.Debugger.Break();
                }

至少它为调试指明了方向,知道问题出在哪里.您可以按照此方向进行进一步的改进.

using NetOffice.WordApi.Enums;
using Word = NetOffice.WordApi;

Test();

//The following code applies only to the content( main body) of the document itself and does not include the footnote, comments, header, footer ......, and other parts of the document.
void Test()
{
    //just test file for me
    //const string fFullnameStr = @"C:\Users\oscar\Dropbox\VS\VBA\stackoverflow.docm";
    const string fFullnameStr = @"C:\Users\oscar\Dropbox\VS\stackoverflow\VBA\Naive Bayes classifier.docx";
    Word.Application wordApplication = new Word.Application();
    wordApplication.DisplayAlerts = WdAlertLevel.wdAlertsNone;
    wordApplication.Visible = true; //just for test to watch
    Word.Document doc = wordApplication.Documents.Open(fFullnameStr);//Context.WordDocument;

    /* for test
    if(doc.ProtectionType!= WdProtectionType.wdAllowOnlyFormFields)
        Console.WriteLine(doc.ProtectionType);
    doc.Close();
    doc.Protect(WdProtectionType.wdAllowOnlyFormFields);
    just for test */
    int i = 0;

    //var searchText = "smth text";
    // https://github.com/Aldman/ProtectedRangeSearch/blob/main/FindTextTests.cs#L15
    var searchText = "based on a common";//"diameter features";//"based on a common";//"assume that the value";
    var bookmarkName = "newBookmark";

    Word.Range rng = doc.Content;//doc.Content.Duplicate;

    if (doc.ProtectionType != WdProtectionType.wdAllowOnlyFormFields)
    {
        if (doc.ActiveWindow.View.ShowFieldCodes)
            doc.ActiveWindow.View.ShowFieldCodes = false;
        while (rng.Find.Execute(findText: searchText, matchCase: true, matchWholeWord: true, matchWildcards: false,
                matchSoundsLike: false, matchAllWordForms: false, forward: true, wrap: WdFindWrap.wdFindStop))
        {
            rng.Bookmarks.Add(bookmarkName + i++.ToString()); //rng.Select();//just for test
        }

    }
    else
    {
        foreach (var paragraph in rng.Paragraphs)//http://msdn.microsoft.com/en-us/en-us/Iibrary/office/ff837006.aspx 轉址為:https://learn.microsoft.com/en-us/office/vba/api/Word.Range.Paragraphs
        {
            Word.Range range = paragraph.Range;
            var text = range.Text;
            var index = text.IndexOf(searchText); int indexPre = index;
            var start = 0;


            #region GetParagraphTextWithHiddenSymbols
            foreach (Word.Field item in range.Fields)
            {

                index = text.IndexOf(item.Result.Text, start);
                if (index >= 0)
                {
                    text = text.Substring(0, index) + "{" + item.Code.Text + "}" + item.Result.Text + ((char)21).ToString()
                        + text.Substring(index + item.Result.Text.Length);
                    start = (text.Substring(0, index) + "{" + item.Code.Text + "}" + item.Result.Text + ((char)21).ToString()).Length;
                }
                //text = text.Replace(item.Result.Text, 
                //"{" +item.Code.Text+"}"+ item.Result.Text + (char)21);
                //fieldsResultLength += item.Result.Text.Length + 2 + 1;//2="{}" of field code,1=chr(21) placehold of the fields
            }

            start = 0;
            //there will be "" both the start and end of a ContentControl object, so have to plus 2 for the two placeholders
            foreach (Word.ContentControl item in range.ContentControls)
            {
                text = text.Substring(start, item.Range.Start - 1) + " " + item.Range.Text + " " + text.Substring(item.Range.End - 1);
            }
            #endregion


            while (index >= 0)
            {

                index = text.IndexOf(searchText);

                start = range.Start;
                var end = range.End;

                start += index; //+ fieldsResultLength;
                end = start + searchText.Length;
                range.SetRange(start, end);

                while (range.Text != searchText && end <= range.End)
                {
                    range.SetRange(++start, ++end);
                    if (range.Text == searchText) break;
                }

                if (range.Text != searchText)
                {
                    Console.WriteLine(range.Text);
                    System.Diagnostics.Debugger.Break();
                }

                range.Bookmarks.Add(bookmarkName + i++.ToString());

                text = paragraph.Range.Text; start = 0;
                index = text.IndexOf(searchText, indexPre + 1);
                indexPre = index;
            }
        }
    }


    wordApplication.Visible = true; //just for test to watch
    doc.ActiveWindow.View.ReadingLayout = false;//just for test to watch
    if (doc.ProtectionType != WdProtectionType.wdNoProtection)
        doc.Unprotect(123.ToString());//just for test

}

当保护类型类似于wdAllowOnlyFormFields时,Find个对象不能执行搜索是逻辑必然的.我认为这是因为Find对象类不仅是一个Find类,而且还包括一个替换(编辑)功能.您需要取消它的保护,或者更改保护它的方式,或者 Select 使用当前的替代方案,我在上面的代码中对这两种方案的流进行了条件调整.除了使用这种foreach paragraph方法进行定位外,您还可以考虑使用正则表达式来实现这一点.无论使用哪种方法,都必须对隐藏的文本进行适当的处理,如Fields‘代码文本,才能获得准确的结果.

  • .csproj文件:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NetOfficeFw.Core" Version="1.9.3" />
    <PackageReference Include="NetOfficeFw.Word" Version="1.9.3" />
  </ItemGroup>

  <ItemGroup>
    <FrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms" />
  </ItemGroup>

</Project>

  • 100来实现
void Test_ShowFieldCodes()
{
    //just test file for me
    const string fFullnameStr = @"C:\Users\oscar\Dropbox\VS\VBA\stackoverflow.docm";
    Word.Application wordApplication = new Word.Application();
    wordApplication.DisplayAlerts = WdAlertLevel.wdAlertsNone;
    //wordApplication.Visible = true; //just for test to watch
    Word.Document doc = wordApplication.Documents.Open(fFullnameStr);//Context.WordDocument;


    int i = 0;
    var searchText = "smth text";
    var bookmarkName = "newBookmark";

    Word.Range rng = doc.Content;//doc.Content.Duplicate;

    if (doc.ProtectionType != WdProtectionType.wdAllowOnlyFormFields)
    {

        while (rng.Find.Execute(findText: searchText, matchCase: true, matchWholeWord: true, matchWildcards: false,
                matchSoundsLike: false, matchAllWordForms: false, forward: true, wrap: WdFindWrap.wdFindStop))
        {

            if ((bool)rng.Information(WdInformation.wdInContentControl))
                rng.SetRange(rng.Paragraphs[1].Range.ContentControls[1].Range.End + 1,
                    rng.Paragraphs[1].Range.ContentControls[1].Range.End + 1);
            rng.Bookmarks.Add(bookmarkName + i++.ToString());
        }

    }
    else
    {        //rng = doc.Content.Duplicate;
        foreach (var paragraph in rng.Paragraphs)//http://msdn.microsoft.com/en-us/en-us/Iibrary/office/ff837006.aspx 轉址為:https://learn.microsoft.com/en-us/office/vba/api/Word.Range.Paragraphs
        {
            Word.Range range = paragraph.Range;
            var text = range.Text;
            var index = text.IndexOf(searchText); int indexPre = 0;
            var start = 0;

            while (index >= 0)
            {

                if (paragraph.Range.Fields.Count > 0)
                {

                    doc.ActiveWindow.View.ShowFieldCodes = true;
                    text = paragraph.Range.Text;
                    //if there are fields this will be the index of ShowFieldCodes=false + index of ShowFieldCodes=true and plus 1
                    index = index + text.IndexOf(searchText, indexPre) + 1;
                    doc.ActiveWindow.View.ShowFieldCodes = false;
                }

                start = range.Start;
                var end = range.End;

                start += index;
                end = start + searchText.Length;
                range.SetRange(start, end);

                while (range.Text != searchText && end <= range.End && range.End < doc.Content.End - 1)
                {
                    //range.Select();//just for test
                    range.SetRange(++start, ++end);
                    if (range.Text == searchText) break;
                }

                if (range.Text != searchText && range.End < doc.Content.End - 1)
                {
                    Console.WriteLine(range.Text);
                    System.Diagnostics.Debugger.Break();
                }

                if (range.Text == searchText)
                {
                    if ((bool)range.Information(WdInformation.wdInContentControl))
                        range.SetRange(range.Paragraphs[1].Range.ContentControls[1].Range.End + 1,
                            range.Paragraphs[1].Range.ContentControls[1].Range.End + 1);
                    range.Bookmarks.Add(bookmarkName + i++.ToString());
                }
                text = paragraph.Range.Text; start = 0;
                index = text.IndexOf(searchText, indexPre + 1);
                indexPre = index;
            }
        }
    }


    wordApplication.Visible = true; //just for test to watch
    //doc.Unprotect(1.ToString());//just for test

}

20230712内容控件

所以答案是,在your file中没有字段,它所有的文件都是ContentControl而不是FieldsActiveDocument.ContentControls.Count 是3.ActiveDocument.Fields.Count是0. 新代码在上面进行了更新.

Csharp相关问答推荐

如何使用Automapper映射两个嵌套列表

Regex在c#中完全匹配

有没有一种方法可以在包含混合文本的标签中嵌入超链接?

通过EFCore上传大量数据.

如何使用ConcurentDictionary属性上的属性将自定义System.Text.Json JsonConverter应用于该属性的值?

如何使用XmlSerializer反序列化字符串数组?

在具有主构造函数的类中初始化属性时出现警告

.NET HttpClient、JsonSerializer或误用的Stream中的内存泄漏?

如何在没有前缀和可选后缀的情况下获取Razor Page Handler方法名称?

MigraDoc文档

如何在C#中实现非抛出`MinBy`?

如何将MemberInitExpression添加到绑定中其他Lambda MemberInitExpression

将内置的OrderedEumable&Quot;类设置为内部类有什么好处?

使用System.Text.Json进行序列化时发生StackOverflow异常

在使用StringBuilder时,如何根据 colored颜色 设置为richTextBox中的特定行着色?

如何避免在.NET中将日志(log)写入相对路径

当我手动停止和关闭系统并打开时,Windows服务未启动

C#定时器回调对象上下文?

SqlException:无法打开数据库.升级到Dotnet 8后-数据库兼容性版本-非EFCore兼容性级别

游戏对象走向不同的方向