I have a list of keywords and I need to highlight it on target page.
I have such code:
let list = ["AMD", "Intel", "ATI", "core", "code", "error"]
function highlight_words(word) {
const page = document.body.innerHTML;
document.body.innerHTML = page.replace(new RegExp(word, "gi"), (match) => `<mark>${match}</mark>`);
}
for (const index in list) {
highlight_words(list[index]);
}
But now it highlights ANY text (even part of it) that contains elements from list.
For example: I have word 'cores', and part of it - "core" will be highlighted.
But it is not correct, because I am looking for "single core" on page ;)
In some cases it even broke the page :(
How I can modify my code to solve my issue?
Related
I'm kind of new to JavaScript, and I hope someone can help me figure out how to develop an application that:
Takes a text value from an HTML input when pressing a button;
Check if that text value has certain syllables (e.g. if it has the syllables "ba", "ca", and "da" in it);
returns a value to the HTML output whether or not it has syllables.
Obs. HTML is no problem. My focus is on JS.
I know it sounds simple, but I would like at least a direction from where to start. I already realized that I will have to have at least two variables, one for the text value
const text = document.getElementById("name").value;
and another one for the syllables that I intend to check
constant syllable = ["ba", "ca", "da", "fa", "ra"];
Am I in the right direction? My problem starts when I try to write the functions. Anyway, I appreciate any help. Thanks.
you can use find on array and includes in a string
const syllable = ["ba", "ca", "da", "fa", "ra"];
validate = (word) => {
if (!word) {
return false;
}
let res = syllable.find(i => word.includes(i));
return res ? true : false
}
console.log(validate("aadaa"))
Your questions is a little vague, but I hope this answers it. I've included some explanation about how it all works.
// You define a function using 'function <identifier>(){<functionbody>}' (generally)
function containsSyllables(text_value, syllables) {
// parameters are set here ^^^^^^^^^^^^^
let foundSyllable = false; // <-- keep track of whether or not we've found a matching syllable
// this loops through each item in `syllables` (we refer to each item as `syllable`)
for (let syllable of syllables) {
// You can use the String.prototype.includes(substring) method to check if a string contains a substring
if (text_value.includes(syllable)) {
foundSyllable // <-- keep track that we've found a syllable in the text_value
break // exit this loop if we found a matching syllable
}
}
return foundSyllable
// return the variable that kept track of whether or not we found a syllable
}
var result = containsSyllables("bad", ["ca", "ba"]);
console.log(`"bad" contains "ca" or "ba"? ${result}`);
// OUTPUTS: "bad" contains "ca" or "ba"? false
There are some improvements you could make to this function, but I think this gets the point across simply.
If there is a line in this that you do not understand, you can search up roughly what it is: e.g. "for loop javascript". Look for a MDN link, they're the best.
You can take your element with getElementById, like you did, add event on it with a function name.addEventListener('input', updateValue); then check if your input includes one of your substrings from array and print a message as result ( check array.some)
const syllable = ["ba", "ca", "da", "fa", "ra"];
const name = document.getElementById("nameInput");
name.addEventListener('input', checkForSyllables);
function checkForSyllables(e) {
const value = e.target.value;
if (syllable.some(syllable => value.includes(syllable))) {
console.log(value) // your word contains one of elements from array
document.getElementById("result").innerHTML = value + " contains syllable";
}
}
<p> Name: </p>
<input type="text" id="nameInput" keyup="onKeyUp">
<p id="result" />
I am trying to find all the text along with the parent tag in the HTML. In the example below, the variable named html has the sample HTML where I try to extract the tags and the text.
This works fine and as expected gives out the tags with the text
Here I have used cheerio to traverse DOM. cheerio works exactly same as jquery.
const cheerio = require("cheerio");
const html = `
<html>
<head></head>
<body>
<p>
Regular bail is the legal procedure through which a court can direct
release of persons in custody under suspicion of having committed an offence,
usually on some conditions which are designed to ensure
that the person does not flee or otherwise obstruct the course of justice.
These conditions may require executing a “personal bond”, whereby a person
pledges a certain amount of money or property which may be forfeited if
there is a breach of the bail conditions. Or, a court may require
executing a bond “with sureties”, where a person is not seen as
reliable enough and may have to present
<em>other persons</em> to vouch for her,
and the sureties must execute bonds pledging money / property which
may be forfeited if the accused person breaches a bail condition.
</p>
</body>
</html>
`;
const getNodeType = function (renderedHTML, el, nodeType) {
const $ = cheerio.load(renderedHTML)
return $(el).find(":not(iframe)").addBack().contents().filter(function () {
return this.nodeType == nodeType;
});
}
let allTextPairs = [];
const $ = cheerio.load(html);
getNodeType(html, $("html"), 3).map((i, node) => {
const parent = node.parentNode.tagName;
const nodeValue = node.nodeValue.trim();
allTextPairs.push([parent, nodeValue])
});
console.log(allTextPairs);
as shown below
But the problem is that the text tags extracted are out of order. If you see the above screenshot, other persons has been reported in the end, although it should occur before to vouch for her .... Why does this happen? How can I prevent this?
You might want to just walk the tree in depth order. Walk function courtesy of this gist.
function walk(el, fn, parents = []) {
fn(el, parents);
(el.children || []).forEach((child) => walk(child, fn, parents.concat(el)));
}
walk(cheerio.load(html).root()[0], (node, parents) => {
if (node.type === "text" && node.data.trim()) {
console.log(parents[parents.length - 1].name, node.data);
}
});
This prints out the stuff, but you could just as well put it in that array of yours.
I'm working on a Word Add-in using Angular, and I'm stuck at this point here.
I'm trying to traverse through the document searching for occurences of a word, but instead of performing an operation on all occurrences, I want to perform an operation on them by navigating to them and determine what to do with them one by one.
I have the following code:
goToNext(searchString: string){
return Word.run(wordContext => {
var results = wordContext.document.body.search(searchString, { matchWholeWord: true, matchCase: true }); //Search for the text to replace
results.getFirst().select();
return wordContext.sync();
});
}
This does select the first occurrence of the search, but calling the function again won't select the next occurrence.
How do I go to the next occurrence by calling that method again?
Wrote this in a comment, but marking this as the answer for others that might be interested
Word.run(wordContext => {
const search = wordContext.document.body.search(searchTerm, { matchWholeWord: wholeWord, matchCase: true });
wordContext.load(search);
return wordContext.sync().then(() => {
search.items[index].select();
});
This selects the word that is searched for based on where it is located in document reading the index value
Use Below function to search for a particular text under word document, make sure to pass correct wordIndex present under document for the word you would like to search
goToNext(searchString: string, wordIndex : string){
Word.run(async function(wordContext) {
var results = wordContext.document.body.search(searchString, { matchWholeWord: true, matchCase: true }); //Search for the text to replace
context.load(results);
return context.sync().then(() => {
search.items[wordIndex].font.highlightColor = '#ffedcc'; //If you want to highlight the selection with background color
search.items[wordIndex].select();
});
});
}
Is it possible to save markup on a JS object to be retrieved later?
Why?
Here lies my problem, if a description is too long, I'd like to be able to break it into separate chunks, perhaps different HTML tags, as opposed to having the entire text in one long chain of words
ie: after looping through object...
<div>{markup.description}</div>
the above would give me all the description data, but I wouldn't able to massage it (break into bold, italic, headings, or spans.) for a better UI.
So the end result that I'm trying to learn here is how to produce something like:
const markup = [
{
name: "<h1>Joe Doe<h1/>",
food: "<p>pizza<p/>",
description: "<h1>super long description<h2><p>bla bla
bla</p>"
}
]
I tried template literals but no dice.
I know I could separate chunks of text by adding more keys in the object, but that feels redundant because it is all a description, besides I still wouldn't be able to apply any styles (add a class) for words that need attention in the middle of the text.
I guess you can always make the properties functions.
const markup = [{
name: () => "<h1>Joe Doe<h1/>",
food: () => "<p>pizza<p/>",
description: () => "<h1>super long description<h2><p>bla bla bla</p>"
}]
function renderMarkup(item) {
let markup = ''
Object.entries(item).forEach(([key, value]) => {
markup += value()
});
return markup
}
$('.markup').html(renderMarkup(markup[0]))
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="markup"></div>
If you're using React and have JSX available, you can store JSX fragments in a variable and then reference them when you render. However, the markup you've written is very malformed. You have closing tags with the slash in the wrong place and you have an h1 matched up with an h2. JSX markup has to be valid, and each fragment has to be enclosed in a tag that contains the whole fragment.
This works:
const markup = [
{
name: <h1>Joe Doe</h1>,
food: <p>pizza</p>,
description: <div><h1>super long description</h1><p>bla bla bla</p></div>
},
{
name: <h1>Janet Doe</h1>,
food: <p>chicken</p>,
description: <div><h1>yet another super long description</h1><p>bla bla bla</p></div>
}
];
const App = () => (
<div>
{ markup.map(r => [r.name, r.food, r.description]) }
</div>
);
I have a string of text that I am storing mark-up for separately. For example:
var content = {
text: "a little white rabbit hops",
style: [
{
type: "strong",
start: 0,
length: 8
},
{
type: "em",
start: 2,
length: 14
}
]
}
I am then parsing that into html, but the em tag has to be opened and closed twice to be properly formatted:
<strong>a <em>little</em></strong><em> white</em> rabbit hops
My question is: what is the best way to parse the html retrieved from the DOM to consolidate the separated em tags (or conceivably the strong tags: in my scenario either could be nested).
If I iterate over a NodeList of children (p.getElementsByTagName('em')) I would have to do multiple for loops and check the start/length of all nested tags. There has got to be an easier way, but I haven't thought of one - is there a library out there that handles this kind of formatting (or a way to to do this directly via the DOM)?
I am not using jQuery and don't want to add it to my project just for this. Any help is much appreciated!
---EDIT---
To clarify the question: this is essentially about translating the formatting into/out of the HTML, the issue is the best way to handle the tag nesting: i.e. even though there are two em child tags, there is really only one em formatted block (the end/start of em child tag 1 and 2 are contiguous)
Here are two functions for the conversion in either direction.
First the one that converts an HTML string to the content structure you described:
function htmlToContent(html) {
// The object to fill and return:
var content = {
text: '',
style: []
};
// Keep track of recently closed tags (i.e. without text following them as of yet)
var closedStyles = [];
// Recursive function
function parseNode(elem) {
var style;
if (elem.nodeType === 3) {
// This is a text node (no children)
content.text += elem.nodeValue;
// Any styles that were closed should be added to the content
// style array, as they cannot be "extended" any more
[].push.apply(content.style, closedStyles);
closedStyles = [];
} else {
// See if we can extend a style that was closed
if (!closedStyles.some(function (closedStyle, idx) {
if (closedStyle.type === elem.nodeName) {
style = closedStyle;
// Style will be extended, so it's no longer closed
closedStyles.splice(idx, 1);
return true; // exit ".some"
}
})) {
// No style could be extended, so we create a new one
style = {
type: elem.nodeName,
start: content.text.length,
length: 0
};
}
// Recurse into the child nodes:
[].forEach.call(elem.childNodes, function(child) {
parseNode(child);
});
// set style length and store it as a closed one
style.length = content.text.length - style.start;
closedStyles.push(style);
}
}
// Create a node with this html
wrapper = document.createElement('p');
wrapper.innerHTML = html;
parseNode(wrapper);
// Flush remaining styles to the result
closedStyles.pop(); // Discard wrapper
[].push.apply(content.style, closedStyles);
return content;
}
This function first injects the HTML string into a DOM wrapper element, and then recurses into the node hierarchy to build the content structure. The main idea in this code is that it first collects closed nodes in a temporary closedStyles array. Only when it becomes certain that these cannot be used anymore for a merge with an upcoming node, they are added to the content structure. This happens when a text node is encoutered. If however tags close and open again without intermediate text, the matching style is located and extracted from this closedStyles array, and reused for extension.
The function that does the opposite could be defined as follows:
function contentToHtml(content) {
var tags = [];
// Build list of opening and closing tags with the offset of injection
content.style.forEach(function (tag) {
tags.push({
html: '<' + tag.type + '>',
offset: tag.start
}, {
html: '</' + tag.type + '>',
offset: tag.start + tag.length
});
});
// Sort this list by decreasing offset:
tags.sort(function(a, b) {
return b.offset - a.offset;
});
var html = '';
var text = content.text;
// Insert opening and closing tags from end to start in text
tags.forEach(function (tag) {
// Prefix the html with the open/close tag and the escaped text that follows it
html = tag.html + textToHtml(text.substr(tag.offset)) + html;
// Reduce the text to the part that still needs to be processed
text = text.substr(0, tag.offset);
});
// Remaining text:
html = textToHtml(text) + html;
// Create a node with this html, in order to get valid html tag sequences
p = document.createElement('p');
p.innerHTML = html;
// p.innerHTML will change here if html was not valid.
return p.innerHTML;
}
This function first translates each of the styles to two objects, one representing the opening tag, and the other the closing tag. These tags are then inserted into the text at the right position (from then of the text to the beginning). Finally the trick you described yourself is applied: the resulting html is put in a dom object and taken out of it again. This way any invalid HTML tag sequence is fixed.
The function uses the textToHtml utility function, which could be defined as follows:
function textToHtml(text) {
// See http://www.w3.org/International/questions/qa-escapes#use
return text.replace('&', '&').replace('<', '<').replace('>', '>');
}
You can see it work in this fiddle, where an example HTML string is used that also includes nested tags of the same type. These are maintained.