Office.js select text and replace it with a ContentControl - javascript

I have the following use case using Office.js:
search for some text with body.search()
after finding the text, can be multiple occurrences, iterate through them and replace them with a ContentControl with a different content
The search part is easy, but I'm not sure about the second part. Inserting a ContentControl to the cursor position and manipulating it's HTML content isn't an issue, but I'm not sure if it's possible to programmatically select a string and then replace it with other content. Is it?
Or should I somehow create a ContentControl around the selected text and then just manipulate it's HTML content?
This is my code so far, within Word.run:
const res = context.document.body.search('[{]*[}]', {matchWildCards: true});
context.load(res, 'text');
return context.sync().then(() => {
const citeKeys = [];
for (let i = 0; i < res.items.length; i += 1) {
// iterate through found strings by accessing res.items[i].text
}
// ...

After you searched the strings, body.search will return a collection to you and you can loop the range collection and call range.insertText("...", "replace"). This insertText method will also return a range and then you can call range.insertContentControl on it. I think this will help you achieve the goal.

Related

How to replace words appearing anywhere through the DOM?

I have 2 arrays, one for old words and one for new ones:
old_words = ['word1', 'word2', 'word3'];
new_words = ['nword1', 'nword2', 'nword3'];
I need to find which words inside the DOM match any in the old_words array, replace it with the same index one in new_words, and wrap it in a tag.
And yes, I know traversing the DOM is probably not the best option, but I don't know where the words might appear since it is meant to work on any website.
I can't figure out how to do this... this is what I have so far:
const regexp = new RegExp('(' + old_words.join('|') + ')', 'ig');
old_words.forEach(function (word, idx) {
let addSpan = word.replace( regexp, `<span data-old-word="$&"> ${new_words[idx]}</span>`;
// that's all I have
});
the thing is, I don't know what to change to check the entire DOM as it fails...any tips would be appreciated...
I have previously attempted to do:
if (document.body.innerText.indexOf(word) !== -1){
//change
}
but it crashes because of the size. I assume I could take the values from the only but still I'm unsure how to go about it.
if you start with an empty html and create the elements using createElement().
or if every element has a class you could do getElementByClass().

Changing the background color of text on a webpage in JavaScript (when element ID or name isn't available)

What I want to achieve is that once a web page is loaded, find some text in it and highlight the text. My code what I've written till now is as follows.
First I match the textContent using a regular expression
(Why am I using this approach is because I don't have the ID or name for the div where the textContent is present, thus, I cant go for getElementsByName() or getElementById()). To check if the text exists, I first compare it's length and then proceed to highlight it.
The text I find is at index 0 of the text variable. I cant access backgroundColor() method that is why it's been commented out for now. I could access fontcolor() but even it didn't seem to work.
I am new to JS so some direction would be greatly appreciated.
I would like to know why fontcolor() doesn't work even though I can access it.
What would be the best approach to solve this as I cant access backgroundColor().
(function highlightText(){
let text = document.body.textContent.match(/abcxyz/);
if (text[0].length == 6)
{
text[0].fontcolor('yellow');
//text[0].backgroundColor('yellow');
console.log(text[0]); //prints abcxyz
return text[0];
}
return null;
})()
SOLUTION:
The approach based off on the solution provided by #forlen:
What I did was loop through all the nodes, if the textContent matches the regular expression then push them into an array and as I knew what I wanted was the root element so I got the last element of the array and changed its backgroundColor.
(function highlightText() {
let allNodes = document.body.getElementsByTagName("*");
var arr= [];
for (let node of allNodes) {
if (node.textContent.match(/abcxyz/)) {
arr.push(node);
}
}
text = arr[(arr.length)-2];
text.style.backgroundColor = "yellow";
})();
I recommend looping through all nodes in body and changing style like this:
(function highlightText() {
let allNodes = document.body.getElementsByTagName("*");
for (let node of allNodes) {
if (node.textContent === "abcxyz") {
node.style.color = "yellow";
}
}
})();

Problems selecting a div depending on text inside

i've been working on a project with puppeteer and it was going great so far until a tried to localize div's depending on their text.
I can't use the div id, name or class, i need to find it by the text inside of it.
My idea is to create an array of all the text i need to find and then loop trought the div's to find the ones that match any of the text in the array.
Can anybody help me find a solution? Thank you!
This is relatively straightforward using vanilla javascript (no JS libraries):
// Get an array of all <divs>:
let allDivs = [... document.getElementsByTagName('div')];
// Loop through allDivs, checking the text of each one:
allDivs.forEach((div) => {
let textInsideDiv = div.textContent;
// THE REST OF YOUR CODE HERE
// if (myArrayOfText.indexOf(textInsideDiv) > -1)) { etc.}
}

Is there a faster way to search for a value in a table?

I have a html table with almost 1000 rows that needs to have a search function. I want to make this search automatic, so it starts searching for the query as the user starts to enter the string. This isn't that hard, but the nature of the search causes the page to slow down for a few seconds after the first character or two are input into my search bar.
Here is the code for the search:
const cols = search_drop.value;
const itemsArr = document.querySelectorAll('tbody tr');
for (const ele of itemsArr) {
const lower_case_search_value = this.value.toLowerCase();
const lower_case_table_value = ele.querySelector(`td[headers="${cols}"]`).innerText.toLowerCase();
if (!lower_case_table_value.includes(lower_case_search_value)) {
ele.style.display = 'none';
} else {
ele.style.display = 'table-row';
}
}
I already tried to search for everything in the background without re-rendering the elements every time, but the slowdown remains. Any tips to speed this up?
My guess, that you can either store td text as a data attribute and take advantages of attribute search (see https://www.w3schools.com/cssref/sel_attr_contain.asp) or you can precalculate indices of a table (matrix) by putting them in relation to a given text, then all you need is to iterate over a collection, which is a linear time (and better in a sense that you do not interact with DOM).

Regex to search html return, but not actual html jQuery

I'm making a highlighting plugin for a client to find things in a page and I decided to test it with a help viewer im still building but I'm having an issue that'll (probably) require some regex.
I do not want to parse HTML, and im totally open on how to do this differently, this just seems like the the best/right way.
http://oscargodson.com/labs/help-viewer
http://oscargodson.com/labs/help-viewer/js/jquery.jhighlight.js
Type something in the search... ok, refresh the page, now type, like, class or class=" or type <a you'll notice it'll search the actual HTML (as expected). How can I only search the text?
If i do .text() it'll vaporize all the HTML and what i get back will just be a big blob of text, but i still want the HTML so I dont lose formatting, links, images, etc. I want this to work like CMD/CTRL+F.
You'd use this plugin like:
$('article').jhighlight({find:'class'});
To remove them:
.jhighlight('remove')
==UPDATE==
While Mike Samuel's idea below does in fact work, it's a tad heavy for this plugin. It's mainly for a client looking to erase bad words and/or MS Word characters during a "publishing" process of a form. I'm looking for a more lightweight fix, any ideas?
You really don't want to use eval, mess with innerHTML or parse the markup "manually". The best way, in my opinion, is to deal with text nodes directly and keep a cache of the original html to erase the highlights. Quick rewrite, with comments:
(function($){
$.fn.jhighlight = function(opt) {
var options = $.extend($.fn.jhighlight.defaults, opt)
, txtProp = this[0].textContent ? 'textContent' : 'innerText';
if ($.trim(options.find.length) < 1) return this;
return this.each(function(){
var self = $(this);
// use a cache to clear the highlights
if (!self.data('htmlCache'))
self.data('htmlCache', self.html());
if(opt === 'remove'){
return self.html( self.data('htmlCache') );
}
// create Tree Walker
// https://developer.mozilla.org/en/DOM/treeWalker
var walker = document.createTreeWalker(
this, // walk only on target element
NodeFilter.SHOW_TEXT,
null,
false
);
var node
, matches
, flags = 'g' + (!options.caseSensitive ? 'i' : '')
, exp = new RegExp('('+options.find+')', flags) // capturing
, expSplit = new RegExp(options.find, flags) // no capturing
, highlights = [];
// walk this wayy
// and save matched nodes for later
while(node = walker.nextNode()){
if (matches = node.nodeValue.match(exp)){
highlights.push([node, matches]);
}
}
// must replace stuff after the walker is finished
// otherwise replacing a node will halt the walker
for(var nn=0,hln=highlights.length; nn<hln; nn++){
var node = highlights[nn][0]
, matches = highlights[nn][1]
, parts = node.nodeValue.split(expSplit) // split on matches
, frag = document.createDocumentFragment(); // temporary holder
// add text + highlighted parts in between
// like a .join() but with elements :)
for(var i=0,ln=parts.length; i<ln; i++){
// non-highlighted text
if (parts[i].length)
frag.appendChild(document.createTextNode(parts[i]));
// highlighted text
// skip last iteration
if (i < ln-1){
var h = document.createElement('span');
h.className = options.className;
h[txtProp] = matches[i];
frag.appendChild(h);
}
}
// replace the original text node
node.parentNode.replaceChild(frag, node);
};
});
};
$.fn.jhighlight.defaults = {
find:'',
className:'jhighlight',
color:'#FFF77B',
caseSensitive:false,
wrappingTag:'span'
};
})(jQuery);
If you're doing any manipulation on the page, you might want to replace the caching with another clean-up mechanism, not trivial though.
You can see the code working here: http://jsbin.com/anace5/2/
You also need to add display:block to your new html elements, the layout is broken on a few browsers.
In the javascript code prettifier, I had this problem. I wanted to search the text but preserve tags.
What I did was start with HTML, and decompose that into two bits.
The text content
Pairs of (index into text content where a tag occurs, the tag content)
So given
Lorem <b>ipsum</b>
I end up with
text = 'Lorem ipsum'
tags = [6, '<b>', 10, '</b>']
which allows me to search on the text, and then based on the result start and end indices, produce HTML including only the tags (and only balanced tags) in that range.
Have a look here: getElementsByTagName() equivalent for textNodes.
You can probably adapt one of the proposed solutions to your needs (i.e. iterate over all text nodes, replacing the words as you go - this won't work in cases such as <tag>wo</tag>rd but it's better than nothing, I guess).
I believe you could just do:
$('#article :not(:has(*))').jhighlight({find : 'class'});
Since it grabs all leaf nodes in the article it would require valid xhtml, that is, it would only match link in the following example:
<p>This is some paragraph content with a link</p>
DOM traversal / selector application could slow things down a bit so it might be good to do:
article_nodes = article_nodes || $('#article :not(:has(*))');
article_nodes.jhighlight({find : 'class'});
May be something like that could be helpful
>+[^<]*?(s(<[\s\S]*?>)?e(<[\s\S]*?>)?e)[^>]*?<+
The first part >+[^<]*? finds > of the last preceding tag
The third part [^>]*?<+ finds < of the first subsequent tag
In the middle we have (<[\s\S]*?>)? between characters of our search phrase (in this case - "see").
After regular expression searching you could use the result of the middle part to highlight search phrase for user.

Categories