GeneralException on insertContentControl() - ms-word

I am having issues with insertContentControl() for some (but not most) of the ranges returned by search().
Essentially, I search for various terms and then for each find, I attempt to create a content control. This mostly work, however every so often I get a GeneralException that looks like this:
{code: "GeneralException", message: "Sorry, something went wrong. Check the OfficeExtension.Error.debugInfo for more information. ", errorLocation: "Range.insertContentControl", statement: "var insertContentControl=v.insertContentControl();", toString: ƒ, …}
code: "GeneralException"
errorLocation: "Range.insertContentControl"
fullStatements: (7) ["var v=context.root._getObjectByReferenceId("{4a645…31b06a1e8cb}{205}") /* originally getItem(28) */;", "var insertContentControl=v.insertContentControl();", "// Instantiate {insertContentControl}", "insertContentControl.title="TypeOne";", "insertContentControl.color="yellow", "insertContentControl.tag="someTag";", "insertContentControl.appearance="Tags";"]
message: "Sorry, something went wrong. Check the OfficeExtension.Error.debugInfo for more information. "
statement: "var insertContentControl=v.insertContentControl();"
surroundingStatements: Array(9)
0: "var v=context.root._getObjectByReferenceId("{4a64573e-3807-4278-a22d-131b06a1e8cb}{205}") /* originally getItem(...) */;"
1: "// >>>>>"
2: "var insertContentControl=v.insertContentControl();"
3: "// <<<<<"
4: "// Instantiate {insertContentControl}"
5: "insertContentControl.title=...;"
6: "insertContentControl.color=...;"
7: "insertContentControl.tag=...;"
8: "insertContentControl.appearance=...;"
lastIndex: (...)
lastItem: (...)
length: 9
Here is a simplified version of my code:
//This object may contain thousands of terms across multiple entity types
let entities = {
EntityType1:{
Term1: [],
Term2: []
}
}
await Word.run(async (context) => {
// search for terms
for(let entityKey in entities) {
let terms = entities[entityKey]
for(let termKey in terms){
let searchResults = context.document.body.search(termKey, {matchWholeWord:true, matchCase: true});
searchResults.load(["text", "parentContentControlOrNullObject"]);
_.extend(terms[termKey], ({"searchHits": searchResults}))
}
}
await context.sync()
//add content controls for each found term
for(let entityKey in entities) {
let terms = entities[entityKey]
for(let termKey in terms){
let term = terms[termKey];
let searchHits = term.searchHits.items;
for (const hit of searchHits) {
//this always works
//hit.font.highlightColor = EntityColors[entityKey]
try {
let cc:Word.ContentControl = hit.parentContentControlOrNullObject
if(cc.isNullObject || cc.tag != appId){
cc = hit.insertContentControl()
}
cc.title = entityKey
cc.color = "yellow"
cc.tag = "someTag"
cc.appearance = "Tags"
//NB. this is here only for debugging - it really slows everything down
await context.sync()
} catch (error) {
if (error instanceof OfficeExtension.Error) {
console.log(error.debugInfo)
console.log(error.innerError)
}
}
}
}
}
await context.sync()
})
I am mainly testing on Word Online but Word desktop seems to display the same issue.
I just can't find what's causing this. What else can I do to find the root of the problem?

As Cindy suggested, I was able to modify your script to stop at the fist exception and find out what's going on. (Good Tip Cindy!).
And this image shows immediately what's the issue (you are trying to insert a CC in the red range, who overlaps with an existing CC, that's an invalid operation):
Use the range.compareLocationWith API to determine you are in a situation like this.

Related

How to loop a search over a long string?

In order to get around the 255 character search limitation in the desktop Word api I'm breaking long strings into searchable chunks of 254 characters and pushing them into an object "oSearchTerms". Then I'm attempting to iterate over oSearchTerms, search for the text, highlight it, then search for the next chunk and do the same until all items in oSearchTerms have been highlighted. The problem is it's not looping. It goes through the first iteration successfully but stops.
I've tried copious context.sync() calls, return true, return context.sync(), etc, which you'll see commented out below, to no avail.
I should also point out that it's not showing any errors. The loop just isn't looping.
Do I have to convert this over to an async function? I'd like to stick with ES5 and not use fat arrow functions.
What am I missing?
var fullSearchTerm = "As discussed earlier, one of the primary objectives of these DYH rules is to ensure that operators have at least one source of XYZ-approved data and documents that they can use to comply with operational requirements The objective would be defeated if the required data and documents were not, in fact, approved and Only by retaining authority to approve these materials can we ensure that they comply with applicable requirements and can be relied upon by operators to comply with operational rules which We believe there are differences between EXSS ICA and other ICA that necessitate approval of EVIS ICA."
function findTextMatch() {
Word.run(function(context) {
OfficeExtension.config.extendedErrorLogging = true;
var oSearchTerms = [];
var maxChars = 254;
var lenFullSearchTerm = fullSearchTerm.length;
var nSearchCycles = Math.ceil(Number((lenFullSearchTerm / maxChars)));
console.log("lenFullSearchTerm: " + lenFullSearchTerm + " nSearchCycles: " + nSearchCycles);
// create oSearchTerms object containing search terms
// leaves short strings alone but breaks long strings into
// searchable 254 character chunks
for (var i = 0; i < nSearchCycles; i++) {
var posStart = i * maxChars;
var mySrch = fullSearchTerm.substr(posStart, maxChars);
console.log( i +" mySrch: "+ mySrch);
var oSrch = {"searchterm":mySrch};
oSearchTerms.push(oSrch);
}
console.log("oSearchTerms.length: " + oSearchTerms.length +" oSearchTerm: "+ JSON.stringify(oSearchTerms));
// Begin search loop
// iterate over oSearchTerms, find and highlight each searchterm
for (var i = 0; i < oSearchTerms.length; i++) {
console.log("oSearchTerms["+i+"].searchterm: " + JSON.stringify(oSearchTerms[i].searchterm));
var searchResults = context.document.body.search(oSearchTerms[i].searchterm, { matchCase: true });
console.log("do context.sync() ");
context.load(searchResults);
return context.sync()
.then(function(){
console.log("done context.sync() ");
console.log("searchResults: "+ JSON.stringify(searchResults));
if(typeof searchResults.items !== undefined){
console.log("i: "+i+ " searchResults: "+searchResults.items.length);
// highlight each result
for (var j = 0; j < searchResults.items.length; j++) {
console.log("highlight searchResults.items["+j +"]");
searchResults.items[j].font.highlightColor = "red";
}
}
else{
console.log("typeof searchResults.items == undefined");
}
// return true;
// return context.sync();
});
//.then(context.sync);
//return true;
} // end search loop
})
.catch( function (error) {
console.log('findTextMatch Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('findTextMatch Debug info: ' + JSON.stringify(error));
}
});
}
I recommend that you not have a context.sync inside a loop. That can be a performance hit and it makes the code hard to reason about. Please see my answer to: Document not in sync after replace text and this sample: Word Add-in Stylechecker for a design pattern that avoids this. The pattern can be used with ES5 syntax if you want.
If you implement this pattern, you may find that the problem has gone away, or at least you will be able to see clearer where the cause might be.

Using Word.SearchOptions in an Office Add-In

I am developing an Office Add-In that processes the text of each paragraph of a Word document against data in JSON format and writes the result to a within the index.html file that is rendered in the Task Pane. This works fine. I am now trying to format the strings within the Word document that correspond to the hits for the keys in the JSON data.
I have a JS block in the head of the index.html file in which I call "Office.initialize" and define a variable based on the JSON data, and have utility functions related to the above functionality. After that comes a function in which I get the Word context and process the Word file paragraphs against the JSON data, and then try to search the Word paragraph itself in order to format the hits. In this last task I am trying to reproduce a snippet from Michael Mainer 1. But no formatting happens when I activate this function. Unfortunately I don't have access to a console since I am on a Mac, which makes it harder to debug.
I would be very appreciative of someone showing me where I'm going wrong.
`function tester() {
Word.run(function (context) {
// Create a proxy object for the document's paragraphs
var paragraphs = context.document.body.paragraphs;
// Load the paragraphs' text, which I run regexes on
context.load(paragraphs, 'text');
return context.sync().then(function () {
for (var i = 0; i < paragraphs.items.length; i++) {
var text = paragraphs.items[i].text;
// jquery to iterate over the "notes" objects from the JSON
$.each(notes, function(key, value) {
var regex = new RegExp("\\b" + key + "\\b", "g");
var res = regex.test(text);
// if the regex hits...
if (res == true) {
// This part works fine, using the JSON data to append to the <DIV> with ID = "notes"
document.getElementById('notes').innerHTML += "<button onclick=hide('" + value.seqNo + "')><b>" + key + "</b></button><p class='" + value.seqNo + "' id='" + i + "'>" + value.notes[0].note + "</p>";
// I now go on to searching for these hits within the current paragraph in the Word file
var thisPara = paragraphs.items[i];
// Set up the search options.
var options = Word.SearchOptions.newObject(context);
options.matchCase = false
// Queue the commmand to search the current paragraph for occurrences of the string "key" (coming from the JSON data)
var searchResults = paragraphs.items[i].search(key, options);
// Load 'text' and 'font' for searchResults.
context.load(searchResults, 'text, font');
// Synchronize the document state by executing the queued-up commands, and return a promise to indicate task completion.
return context.sync().then(function () {
// Queue a command to change the font for each found item.
for (var j = 0; j < searchResults.items.length; j++) {
searchResults.items[j].font.color = '#FF0000'
searchResults.items[j].font.highlightColor = '#FFFF00';
searchResults.items[j].font.bold = true;
}
// Synchronize the document state by executing the queued-up commands,
// and return a promise to indicate task completion.
return context.sync();
});
}
});
}
});
})
.catch(function (error) {
console.log('Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
}
`
It looks like you just need access to the font property, have you tried just:
context.load(searchResults, 'font');
That was working for me?

Relational Queries In parse.com (Unity)

I found example in parse.com. I have 2 objects : Post and Comment, in the Comment objects have a collumn: "parent" pointer to Post obj and I want to join them:
var query = ParseObject.GetQuery ("Comment");
// Include the post data with each comment
query = query.Include("parent");
query.FindAsync().ContinueWith(t => {
IEnumerable<ParseObject> comments = t.Result;
// Comments now contains the last ten comments, and the "post" field
// contains an object that has already been fetched. For example:
foreach (var comment in comments)
{
// This does not require a network access.
string o= comment.Get<string>("content");
Debug.Log(o);
try {
string post = comment.Get<ParseObject>("parent").Get<string>("title");
Debug.Log(post);
} catch (Exception ex) {
Debug.Log(ex);
}
}
});
It worked!
And then, I have 2 objects: User and Gamescore, in the Gamescore objects have a collumn: "playerName" pointer to Post obj I want join them too:
var query = ParseObject.GetQuery ("GameScore");
query.Include ("playerName");
query.FindAsync ().ContinueWith (t =>{
IEnumerable<ParseObject> result = t.Result;
foreach (var item in result) {
Debug.Log("List score: ");
int score = item.Get<int>("score");
Debug.Log(score);
try {
var obj = item.Get<ParseUser>("playerName");
string name = obj.Get<string>("profile");
//string name = item.Get<ParseUser>("playerName").Get<string>("profile");
Debug.Log(name);
} catch (Exception ex) {
Debug.Log(ex);
}
}
});
but It isn't working, Please help me!
Why didn't you do the following like you did your first example:
query = query.Include ("playerName");
you just have -
query.Include ("playerName");
One solution would be to ensure that your ParseUser object is properly fetched. ie:
var obj = item.Get<ParseUser>("playerName");
Task t = obj.FetchIfNeededAsync();
while (!t.IsCompleted) yield return null;
Then you can do this without worrying:
string name = obj.Get<string>("profile");
But that will be another potential request to Parse, which is unfortunate. It seems that query.Include ("playerName") isn't properly working in the Unity version of Parse?
I believe you're supposed to use multi-level includes for this, like .Include("parent.playerName") in your first query.

How can I get an alert if a specific url substring is present and nothing if null

function getCode() {
if (window.location.href.indexOf("?discount=")) {
var url = (document.URL);
var id = url.substring(url.lastIndexOf('=') + 1);
window.alert(id);
return true;
} else {
return false;
}
}
Purpose: When people go to our "Service Request" page using a QR code that has a substring of ?discount=1234. I have been testing by creating an alert box with the discount code showing. Eventually I want to be able to populate that "1234" automatically into a "Discount Code:" text field on page load.
The above is a mixture of a few suggestions when I researched it.
Result: Going to example.com/serviceRequest.html?discount=1234 gives me the appropriate alert "1234", as I want... Going to example.com/serviceRequest.html gives me the alert http://example.com/serviceRequest.html, but I don't want anything to happen if "?discount=" is null.
Any suggestions?
indexOf returns -1 if the search pattern doesn't exist. In JavaScript, anything not a 0 or false or undefined is considered true.
So your line of:
if(window.location.href.indexOf("?discount=")) {
Would better search as:
if(window.location.href.indexOf("?discount=") > -1) {
Try changing your if-statement to:
if(window.location.href.indexOf("?discount=") != -1)
Look up the documentation for ".indexOf". It returns -1 for not found and >= 0 if it is found.
...indexOf("?discount=") >= 0
substring and indexOf return -1 if the text is not found, so you can test for this. E.g.
function getCode() {
if(window.location.href.indexOf("?discount=") != -1) {
var url = (document.URL);
var id = url.substring(url.lastIndexOf('=') + 1);
window.alert(id);
return true;
}
else {
return false;
}
}
You just need to test the indexOf value:
function getCode() {
if (window.location.href.indexOf("?discount=") !== -1) {
var url = (document.URL);
var id = url.substring(url.lastIndexOf('=') + 1);
window.alert(id);
return true;
}
else {
return false;
}
}
So the quick and dirty answer would be
var discount = window.location.search.split("?discount=")[1];
alert(discount);
But this doesn't take into account the occurence of other query string parameters.
You'll really want to parse all the query parameters into a hash map.
This article does a good job of showing you a native and jQuery version.
http://jquery-howto.blogspot.com/2009/09/get-url-parameters-values-with-jquery.html

tinymce.dom.replace throws an exception concerning parentNode

I'm writing a tinyMce plugin which contains a section of code, replacing one element for another. I'm using the editor's dom instance to create the node I want to insert, and I'm using the same instance to do the replacement.
My code is as follows:
var nodeData =
{
"data-widgetId": data.widget.widgetKey(),
"data-instanceKey": "instance1",
src: "/content/images/icon48/cog.png",
class: "widgetPlaceholder",
title: data.widget.getInfo().name
};
var nodeToInsert = ed.dom.create("img", nodeData);
// Insert this content into the editor window
if (data.mode == 'add') {
tinymce.DOM.add(ed.getBody(), nodeToInsert);
}
else if (data.mode == 'edit' && data.selected != null) {
var instanceKey = $(data.selected).attr("data-instancekey");
var elementToReplace = tinymce.DOM.select("[data-instancekey=" + instanceKey + "]");
if (elementToReplace.length === 1) {
ed.dom.replace(elementToReplace[0], nodeToInsert);
}
else {
throw new "No element to replace with that instance key";
}
}
TinyMCE breaks during the replace, here:
replace : function(n, o, k) {
var t = this;
if (is(o, 'array'))
n = n.cloneNode(true);
return t.run(o, function(o) {
if (k) {
each(tinymce.grep(o.childNodes), function(c) {
n.appendChild(c);
});
}
return o.parentNode.replaceChild(n, o);
});
},
..with the error Cannot call method 'replaceChild' of null.
I've verified that the two argument's being passed into replace() are not null and that their parentNode fields are instantiated. I've also taken care to make sure that the elements are being created and replace using the same document instance (I understand I.E has an issue with this).
I've done all this development in Google Chrome, but I receive the same errors in Firefox 4 and IE8 also. Has anyone else come across this?
Thanks in advance
As it turns out, I was simply passing in the arguments in the wrong order. I should have been passing the node I wanted to insert first, and the node I wanted to replace second.