Document not in sync after replace text

懵懂的女人 提交于 2019-12-19 10:52:43

问题


I'm trying to replace some text within a Word Online document but cannot get it to work.

'{{test}} , [[test]] , {test}' results in '13 , 2 , 3' and not '1 , 2 , 3'.

The first text seems to be processed twice.

Any help much appreciated!

Office.initialize = function(reason) {

    function ready() {

        var myTags = [
            { "value": "1", "text": "{{test}}" },
            { "value": "2", "text": "[[test]]" },
            { "value": "3", "text": "{test}" }
        ];

        async function FillTag(tag) {

            await Word.run(async function(context) {

                    var options = Word.SearchOptions.newObject(context);
                    options.matchWildCards = false;

                    var searchResults = context.document.body.search(tag.text, options);
                    context.load(searchResults, 'text');

                    await context.sync();

                    searchResults.items.forEach(function(item) {
                        item.insertText(tag.value, Word.InsertLocation.replace);
                    });
                    await context.sync();
                })
                .catch(function(error) {
                    console.log('Error: ' + JSON.stringify(error));
                    if (error instanceof OfficeExtension.Error) {
                        console.log('Debug info: ' + JSON.stringify(error.debugInfo));
                    }
                });
        }

        async function ProcessArray(myTags) {
            myTags.forEach(async function(tag) {
                await FillTag(tag);
            });
        }

        ProcessArray(myTags);
    }

    if (document.readyState !== 'loading') {
        ready();
    }
    else {
        document.addEventListener('DOMContentLoaded', ready);
    }
};

回答1:


This is more a debugging suggestion than an answer, but it can be edited later. Please install the Script Lab tool from AppSource into Word. One of the sample snippets that you'll find in it is called Search. One of the functions in the snippet is basicSearch. I replaced the search text "Online" with "{{test}}" and I replaced the line that highlights found text in yellow with the following line:

results.items[i].insertText("1", Word.InsertLocation.replace);

This worked fine, so in simple enough scenarios, it can find and replace "{{test}}" accurately.

Could you please try this yourself and then gradually change the method to more closely resemble yours and see at what point it begins to break?


Edit 1/15/18:

@Kim Brandl's answer is probably the best for you, assuming that you really have just 3 search strings. It does, however, have a context.sync inside a loop. Since each sync is a roundtrip to the Office host, that can be a peformance problem when the number of inputs is large and/or the add-in is running in Office Online (which means the Office host is across the internet instread of the same machine).

For anyone reading this who has a large number of input strings, here's a solution that guarantees no more than 3 syncs are needed in the entire Word.run. It also directly attacks the source of the problem that you are trying to solve, which is the relative locations of some found ranges to others (specifically, some are inside others).

The strategy, which I also used in Word-Add-in-Angular2-StyleChecker, is to first load all the ranges and then use the Range.compareLocationWith method and the LocationRelation enum to find the relative location information you need. Finally, use each range's relative location to other ranges to determine whether/how to process it.

Here's the function. Following Kim's example I put the whole snippet in this gist, which you can import to Script Lab tool from AppSource . (See instructions in Kim Brandl's answer.)

async function FindAndReplace() {

    let myTags = [
        { "value": "1", "text": "{{test}}" },
        { "value": "2", "text": "[[test]]" },
        { "value": "3", "text": "{test}" },
        { "value": "4", "text": "bob" },
        { "value": "5", "text": "bobb" },
        { "value": "6", "text": "ssally" },
        { "value": "7", "text": "sally" }
    ];

    let allSearchResults = [];

    await Word.run(async (context) => {    
        for (let tag of myTags) {    
            let options = Word.SearchOptions.newObject(context);
            options.matchWildCards = false;
            let searchResults = context.document.body.search(tag.text, options);
            searchResults.load('text');

            // Store each set of found ranges and the text that should replace 
            // them together, so we don't have to reconstruct the correlation 
            // after the context.sync.
            let correlatedSearchResult = {
                searchHits: searchResults, 
                replacementString: tag.value
            }           
            allSearchResults.push(correlatedSearchResult);       
        }

        await context.sync();

        // Now that we've loaded the found ranges we correlate each to
        // its replacement string, and then find each range's location relation
        // to every other. For example, 'bob' would be Inside 'xbobx'. 
        let correlatedFoundRanges = [];
        allSearchResults.forEach(function (correlatedSearchResult) {
            correlatedSearchResult.searchHits.items.forEach(function (foundRange) {
                let correlatedFoundRange = {
                    range: foundRange,
                    replacementText: correlatedSearchResult.replacementString,
                    locationRelations: []
                }
                correlatedFoundRanges.push(correlatedFoundRange);                
            });
        });

        // Two-dimensional loop over the found ranges to find each one's 
        // location relation with every other range.
        for (let i = 0; i < correlatedFoundRanges.length; i++) {
            for (let j = 0; j < correlatedFoundRanges.length; j++) {
                if (i !== j) // Don't need the range's location relation with itself.
                {
                    let locationRelation = correlatedFoundRanges[i].range.compareLocationWith(correlatedFoundRanges[j].range);
                    correlatedFoundRanges[i].locationRelations.push(locationRelation);
                }
            }
        }

        // It is not necesary to *explicitly* call load() for the 
        // LocationRelation objects, but a sync is required to load them.
        await context.sync();    

        let nonReplaceableRanges = [];
        correlatedFoundRanges.forEach(function (correlatedFoundRange) {
            correlatedFoundRange.locationRelations.forEach(function (locationRelation) {
                switch (locationRelation.value) {
                    case "Inside":
                    case "InsideStart":
                    case "InsideEnd":

                        // If the range is contained inside another range,
                        // blacklist it.
                        nonReplaceableRanges.push(correlatedFoundRange);
                        break;
                    default:
                        // Leave it off the blacklist, so it will get its 
                        // replacement string.
                        break;
                }
            });
        });

        // Do the replacement, but skip the blacklisted ranges.
        correlatedFoundRanges.forEach(function (correlatedFoundRange) {
            if (nonReplaceableRanges.indexOf(correlatedFoundRange) === -1) {
                correlatedFoundRange.range.insertText(correlatedFoundRange.replacementText, Word.InsertLocation.replace);
            }
        })

        await context.sync();
    });
}



回答2:


In your ProcessArray() function, try replacing the forEach statement with a for...of statement, as shown here:

async function ProcessArray(myTags) {
    for (var tag of myTags) {
        await FillTag(tag);
    }
}

Seems that the forEach statement fires off multiple asynchronous calls, without actually awaiting the completion of FillTag each time. If you replace the forEach statement with for...of as shown above, you should get the expected result.


UPDATE (additional info re code structure):

@DutchDan -- now that your initial issue has been resolved, here's a more optimal way to structure your code.

Office.initialize = function () {
    $(document).ready(function () {        
        FindAndReplace();
    });
};

async function FindAndReplace() {

    var myTags = [
        { "value": "1", "text": "{{test}}" },
        { "value": "2", "text": "[[test]]" },
        { "value": "3", "text": "{test}" }
    ];

    await Word.run(async (context) => {

        for (var tag of myTags) {
            var options = Word.SearchOptions.newObject(context);
            options.matchWildCards = false;

            var searchResults = context.document.body.search(tag.text, options);

            context.load(searchResults, 'text');

            await context.sync();

            searchResults.items.forEach(function (item) {
                item.insertText(tag.value, Word.InsertLocation.replace);
            });

            await context.sync();
        }
    }).catch(errorHandler);
}

Note: You can quickly and easily try this snippet yourself by using Script Lab (https://aka.ms/getscriptlab). Simply install the Script Lab add-in (free), then choose "Import" in the navigation menu, and use the following Gist URL: https://gist.github.com/kbrandl/b0c9d9ce0dd1ef16d61372cb84636898.



来源:https://stackoverflow.com/questions/48227941/document-not-in-sync-after-replace-text

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!