Add arrays into multi-dimensional array or object - javascript

I'm parsing content generated by a wysiwyg into a table of contents widget in React.
So far I'm looping through the headers and adding them into an array.
How can I get them all into one multi-dimensional array or object (what's the best way) so that it looks more like:
h1-1
h2-1
h3-1
h1-2
h2-2
h3-2
h1-3
h2-3
h3-3
and then I can render it with an ordered list in the UI.
const str = "<h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3>";
const patternh1 = /<h1>(.*?)<\/h1>/g;
const patternh2 = /<h2>(.*?)<\/h2>/g;
const patternh3 = /<h3>(.*?)<\/h3>/g;
let h1s = [];
let h2s = [];
let h3s = [];
let matchh1, matchh2, matchh3;
while (matchh1 = patternh1.exec(str))
h1s.push(matchh1[1])
while (matchh2 = patternh2.exec(str))
h2s.push(matchh2[1])
while (matchh3 = patternh3.exec(str))
h3s.push(matchh3[1])
console.log(h1s)
console.log(h2s)
console.log(h3s)

I don't know about you, but I hate parsing HTML using regexes. Instead, I think it's a better idea to let the DOM handle this:
const str = `<h1>h1-1</h1>
<h3>h3-1</h3>
<h3>h3-2</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();
let tree = [];
let leaf = null;
for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
const nodeLevel = parseInt(node.tagName[1]);
const newLeaf = {
level: nodeLevel,
text: node.textContent,
children: [],
parent: leaf
};
while (leaf && newLeaf.level <= leaf.level)
leaf = leaf.parent;
if (!leaf)
tree.push(newLeaf);
else
leaf.children.push(newLeaf);
leaf = newLeaf;
}
console.log(tree);
This answer does not require h3 to follow h2; h3 can follow h1 if you so please. If you want to turn this into an ordered list, that can also be done:
const str = `<h1>h1-1</h1>
<h3>h3-1</h3>
<h3>h3-2</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();
let tree = [];
let leaf = null;
for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
const nodeLevel = parseInt(node.tagName[1]);
const newLeaf = {
level: nodeLevel,
text: node.textContent,
children: [],
parent: leaf
};
while (leaf && newLeaf.level <= leaf.level)
leaf = leaf.parent;
if (!leaf)
tree.push(newLeaf);
else
leaf.children.push(newLeaf);
leaf = newLeaf;
}
const ol = document.createElement("ol");
(function makeOl(ol, leaves) {
for (const leaf of leaves) {
const li = document.createElement("li");
li.appendChild(new Text(leaf.text));
if (leaf.children.length > 0) {
const subOl = document.createElement("ol");
makeOl(subOl, leaf.children);
li.appendChild(subOl);
}
ol.appendChild(li);
}
})(ol, tree);
// add it to the DOM
document.body.appendChild(ol);
// or get it as text
const result = ol.outerHTML;
Since the HTML is parsed by the DOM and not by a regex, this solution will not encounter any errors if the h1 tags have attributes, for example.

You can simply gather all h* and then iterate over them to construct a tree as such:
Using ES6 (I inferred this is ok from your usage of const and let)
const str = `
<h1>h1-1</h1>
<h2>h2-1</h2>
<h3>h3-1</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>
`
const patternh = /<h(\d)>(.*?)<\/h(\d)>/g;
let hs = [];
let matchh;
while (matchh = patternh.exec(str))
hs.push({ lev: matchh[1], text: matchh[2] })
console.log(hs)
// constructs a tree with the format [{ value: ..., children: [{ value: ..., children: [...] }, ...] }, ...]
const add = (res, lev, what) => {
if (lev === 0) {
res.push({ value: what, children: [] });
} else {
add(res[res.length - 1].children, lev - 1, what);
}
}
// reduces all hs found into a tree using above method starting with an empty list
const tree = hs.reduce((res, { lev, text }) => {
add(res, lev-1, text);
return res;
}, []);
console.log(tree);
But because your html headers are not in a tree structure themselves (which I guess is your use case) this only works under certain assumptions, e.g. you cannot have a <h3> unless there's a <h2> above it and a <h1> above that. It will also assume a lower-level header will always belong to the latest header of an immediately higher level.
If you want to further use the tree structure for e.g. rendering a representative ordered-list for a TOC, you can do something like:
// function to render a bunch of <li>s
const renderLIs = children => children.map(child => `<li>${renderOL(child)}</li>`).join('');
// function to render an <ol> from a tree node
const renderOL = tree => tree.children.length > 0 ? `<ol>${tree.value}${renderLIs(tree.children)}</ol>` : tree.value;
// use a root node for the TOC
const toc = renderOL({ value: 'TOC', children: tree });
console.log(toc);
Hope it helps.

What you want to do is known as (a variant of a) document outline, eg. creating a nested list from the headings of a document, honoring their hierarchy.
A simple implementation for the browser using the DOM and DOMParser APIs goes as follows (put into a HTML page and coded in ES5 for easy testing):
<!DOCTYPE html>
<html>
<head>
<title>Document outline</title>
</head>
<body>
<div id="outline"></div>
<script>
// test string wrapped in a document (and body) element
var str = "<html><body><h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3></body></html>";
// util for traversing a DOM and emit SAX startElement events
function emitSAXLikeEvents(node, handler) {
handler.startElement(node)
for (var i = 0; i < node.children.length; i++)
emitSAXLikeEvents(node.children.item(i), handler)
handler.endElement(node)
}
var outline = document.getElementById('outline')
var rank = 0
var context = outline
emitSAXLikeEvents(
(new DOMParser()).parseFromString(str, "text/html").body,
{
startElement: function(node) {
if (/h[1-6]/.test(node.localName)) {
var newRank = +node.localName.substr(1, 1)
// set context li node to append
while (newRank <= rank--)
context = context.parentNode.parentNode
rank = newRank
// create (if 1st li) or
// get (if 2nd or subsequent li) ol element
var ol
if (context.children.length > 0)
ol = context.children[0]
else {
ol = document.createElement('ol')
context.appendChild(ol)
}
// create and append li with text from
// heading element
var li = document.createElement('li')
li.appendChild(
document.createTextNode(node.innerText))
ol.appendChild(li)
context = li
}
},
endElement: function(node) {}
})
</script>
</body>
</html>
I'm first parsing your fragment into a Document, then traverse it to create SAX-like startElement() calls. In the startElement() function, the rank of a heading element is checked against the rank of the most recently created list item (if any). Then a new list item is appended at the correct hierarchy level, and possibly an ol element is created as container for it. Note the algorithm as it is won't work with "jumping" from h1 to h3 in the hierarchy, but can be easily adapted.
If you want to create an outline/table of content on node.js, the code could be made to run server-side, but requires a decent HTML parsing lib (a DOMParser polyfill for node.js, so to speak). There are also the https://github.com/h5o/h5o-js and the https://github.com/hoyois/html5outliner packages for creating outlines, though I haven't tested those. These packages supposedly can also deal with corner cases such as heading elements in iframe and quote elements which you generally don't want in the the outline of your document.
The topic of creating an HTML5 outline has a long history; see eg. http://html5doctor.com/computer-says-no-to-html5-document-outline/. HTML4's practice of using no sectioning roots (in HTML5 parlance) wrapper elements for sectioning and placing headings and content at the same hierarchy level is known as "flat-earth markup". SGML has the RANK feature for dealing with H1, H2, etc. ranked elements, and can be made to infer omitted section elements, thus automatically create an outline, from HTML4-like "flat earth markup" in simple cases (eg. where only section or another single element is allowed as sectioning root).

I'll use a single regex to get the <hx></hx> contents and then sort them by x using methods Array.reduce.
Here is the base but it's not over yet :
// The string you need to parse
const str = "\
<h1>h1-1</h1>\
<h2>h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2</h1>\
<h2>h2-2</h2>\
<h3>h3-2</h3>";
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = str.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
tmp[hNumber].push(x.substr(4, x.length - 9));
return tmp;
}, {}));
console.log(matchesSorted);
As you are parsing html content I want to aware you about special cases like presency of \n or space. For example look at the following non-working snippet :
// The string you need to parse
const str = "\
<h1>h1-1\n\
</h1>\
<h2> h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2 </h1>\
<h2>h2-2 \n\
</h2>\
<h3>h3-2</h3>";
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = str.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
tmp[hNumber].push(x.substr(4, x.length - 9));
return tmp;
}, {}));
console.log(matchesSorted);
We gotta add .replace() and .trim() in order to remove unwanted \n and spaces.
Use this snippet
// The string you need to parse
const str = "\
<h1>h1-1\n\
</h1>\
<h2> h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2 </h1>\
<h2>h2-2 \n\
</h2>\
<h3>h3-2</h3>";
// Remove all unwanted \n
const preparedStr = str.replace(/(\r\n\t|\n|\r\t)/gm, "");
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = preparedStr.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
// call trim() to remove unwanted spaces
tmp[hNumber].push(x.substr(4, x.length - 9).trim());
return tmp;
}, {}));
console.log(matchesSorted);

I write this code works with JQuery. (Please don't DV. Maybe someone needs a jquery answer later)
This recursive function creates lis of string and if one item has some childern, it will convert them to an ol.
const str =
"<div><h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3></div><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3>";
function strToList(stri) {
const tags = $(stri);
function partToList(el) {
let output = "<li>";
if ($(el).children().length) {
output += "<ol>";
$(el)
.children()
.each(function() {
output += partToList($(this));
});
output += "</ol>";
} else {
output += $(el).text();
}
return output + "</li>";
}
let output = "<ol>";
tags.each(function(itm) {
output += partToList($(this));
});
return output + "</ol>";
}
$("#output").append(strToList(str));
li {
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
(This code can be converted to pure JS easily)

Related

Passing Nodes to template literal & rendering it?

I have a function returning a template literal:
function generateStuff(nodes) {
const output = `<ul>${nodes}</ul>`;
return document.body.innerHTML = output;
}
nodes is an array of <li> elements made with createElement and then added to nodes via appendChild.
Is there a way of render a list with generateStuff(nodes)? Right now all it returns is <ul>[object NodeList]</ul> and I just want it to return proper HTML with working links etc.
I suppose I need to parse it somehow but as this is JS and NodeList is native to it maybe there's a method in place for it already?
You could use document.createElement and Element#append.
function generateStuff(nodes) {
const el = document.createElement('ul');
el.append(...nodes);
document.body.append(el);
return el.outerHTML;
}
console.log(generateStuff([...Array(3)].map((_, i) => Object.assign(document.createElement('li'), {textContent: i}))));
Is there a way of render a list with generateStuff(nodes)?
Yes, but I'd consider changing how you're doing this instead. But the way you'd do it is with a round-trip through HTML:
function generateStuff(nodes) {
const output = `<ul>${Array.from(nodes, (node) => node.outerHTML)}</ul>`;
return (document.body.innerHTML = output);
}
But that's inefficient and lossy (loses event handlers on the nodes, for instance). Instead of using a template literal, consider appending the nodes directly:
function generateStuff(nodes) {
const ul = document.createElement("ul");
for (const node of nodes) {
ul.appendChild(node);
}
// Or replace the loop with: `ul.append(...nodes)`
document.body.innerHTML = "";
document.body.appendChild(ul);
return ul.outerHTML; // **If** you really need to return HTML, but hopefully not
}
In a comment you've said:
I think I might have overly simplified my case, normally I wouldn't have use template literal here but instead of I have like 20 nested containers there. Your second approach seems really cool but what if ul is inside of a x different containers? How do I append to then? Do I still have to manually create and append every single one of them? That's what I'm trying to avoid here.
You could create the structure by assigning to innerHTML with a means of identifying the ul, then once the structure exists, do the append:
function generateStuff(nodes) {
// Create a new replacement `body` element
const body = document.createElement("body");
// Create the structure
body.innerHTML = "<ul class='target'></ul>";
// Get the `ul` and append to it
const ul = body.querySelector(".target");
ul.append(...nodes);
// Replace the element
document.body.replaceWith(body);
// **If** you really need to return HTML
return document.body.innerHTML;
}
Live Example:
document.querySelector("input[type=button]").addEventListener("click", () => {
const nodes = Array.from({length: 5}, (_, i) => {
const li = document.createElement("li");
li.textContent = "li #" + i;
return li;
});
generateStuff(nodes);
});
function generateStuff(nodes) {
// Create a new replacement `body` element
const body = document.createElement("body");
// Create the structure
body.innerHTML = "<ul class='target'></ul>";
// Get the `ul` and append to it
const ul = body.querySelector(".target");
ul.append(...nodes);
// Replace the element
document.body.replaceWith(body);
// **If** you really need to return HTML
return document.body.innerHTML;
}
<input type="button" value="Go!">

Split any html element separately using regex in JavaScript split

what I'm trying to achieve is when I split the inner contents of an element, I get each item seperate, but the html element needs to be 1 element in the split.
For example:
<p id="name1" class=""> We deliver
<span class="color-secondary">software</span> &
<span class="color-secondary">websites</span> for your organization<span class="color-secondary">.</span>
</p>
Like in the example above, I want to make anything inside the <span> 1 array item after splitting the inner contents of #name1.
So in other words, I want the split array to look like this:
[
'we',
'deliver',
'<span class="color-secondary">software</span>',
'&',
'<span class="color-secondary">websites</span>'
... etc.
]
Currently this is what I have. But this does not work since it ignores the text inside of the html element and therefore splits it halfway through the element. I would also like it to be any html element, and not just limited to <span>.
let sentence = el.innerHTML; // el being #name1 in this case
let words = sentence.split(/\s(?=<span)/i);
How would I be able to achieve this with regex? Is this possible? Thank you for any help.
Here is a DOMParser based solution which parses the HTML and then iterates over the top node's children, pushing the HTML into the result array if the node is an element, or splitting the text on space (if it is a text element) and adding those values to the result array:
const html = `<p id="name1" class=""> We deliver
<span class="color-secondary">software</span> &
<span class="color-secondary">websites</span> for your organization<span class="color-secondary">.</span>
</p>`
const parser = new DOMParser();
const s = parser.parseFromString(html, 'text/html');
let result = [];
for (el of s.body.firstChild.childNodes) {
if (el.nodeType == 3 /* TEXT_NODE */ ) {
result = result.concat(el.nodeValue.trim().split(' ').filter(Boolean));
}
else if (el.nodeType == 1 /* ELEMENT_NODE */ ) {
result.push(el.outerHTML);
}
}
console.log(result);
Details are commented in example below
const nodeSplitter = (mainNode) => {
let scan;
/*
Check if initial node has text or elements
*/
if (mainNode.hasChildNodes) {
scan =
/*
Collect all elements, text, and comments
into an array
*/
Array.from(mainNode.childNodes)
/*
If node is an element, return it...
...if node is text, use `.matchAll()` to
find each word and add to array...
.filter() any falsy values and flatten
the array and then return it
*/
.flatMap(node => {
if (node.nodeType === 1) {
return node;
} else if (node.nodeType === 3) {
const rgx = new RegExp(/[\w\\\-\.\]\&]+/, 'g');
let strings = [...node.textContent.matchAll(rgx)]
.filter(node => node).flat()
return strings;
} else {
/*
Otherwise, return empty array which is
basically nothing since .flatMap()
flattens an array as default
*/
return [];
}
});
} else {
// Return if mainNode is empty
return;
}
// return results
return scan;
}
const main = document.getElementById('name1');
console.log(nodeSplitter(main));
<p id="name1" class=""> We deliver
<span class="color-secondary">software</span> &
<span class="color-secondary">websites</span> for your organization
<span class="color-secondary">.</span>
</p>

Problems when parsing nested html tags from string

I have this code that's to parse a string into html and display the text of each element.
That's working good except when I have nested tags for example <div><p>Element 1</p><p>Element 2</p></div>. In this case, the code displays <p>Element 1</p><p>Element 2</p>.
How can I do to get each tags one after the other ? (Here I want Element 1 and then Element 2)
Here's the code :
let text = new DOMParser().parseFromString(stringHtml, 'text/html');
let textBody = text.body.firstChild;
while (textBody) {
alert(textBody.innerHTML);
// other actions on the textBody element
textBody = textBody.nextSibling;
}
Thanks for helping me out
It sounds like you want a recursive function that prints the textContent of itself, or of its children, if it has children:
const stringHtml = '<div><p>Element 1</p><p>Element 2</p></div><div><p>Element 3</p><p>Element 4</p></div>';
const doc = new DOMParser().parseFromString(stringHtml, 'text/html');
const showElms = parent => {
const { children } = parent;
if (children.length) Array.prototype.forEach.call(children, showElms);
else console.log(parent.textContent);
}
showElms(doc.body);
That's assuming you want to iterate over the actual elements. If you want all text nodes instead, then recursively iterate over the childNodes instead.

How to check if an element has duplicated attributes with cheerio js

I'm parsing HTML files with cheerio (to later test with Mocha), and the HTML elements in these files can have lots of attributes, I want to check if the attribute is repeated within the same element:
example partial file that has an element with repeated "class" attribute:
<div class="logo-center" data-something-very-long="something long" ... class="logo" data-more-stuff>
Here is the code that loads the file:
var fileContents = fs.readFileSync(file, "utf8");
var $ = cheerio.load(fileContents);
Note: it doesn't have to be a class attribute, it could be any other attribute that repeats.
Parse the element under test again. For that to work, you need to dive a bit into the raw DOM object produced by cheerio/htmlparser2. It uses properties that are documented for domhandler, but not for cheerio, so some care with the versions might be needed. I have tested with
└─┬ cheerio#1.0.0-rc.1
├─┬ htmlparser2#3.9.2
│ ├── domhandler#2.4.1
I have formulated this ES6-style, but you could do the same as easily with older, more conventional constructs.
The RegExp may need some refining, though, depending on your expectations on the files you are testing.
const fileContents = fs.readFileSync(file, "utf8");
const $ = cheerio.load(fileContents, {
useHtmlParser2: true,
withStartIndices: true,
withEndIndices: true
});
function getDuplicateAttributes ($elem) {
const dom = $elem.get(0);
// identify tag text position in string
const start = dom.startIndex;
const end = dom.children.length ? dom.children[0].startIndex : dom.endIndex + 1;
// extract
const html = fileContents.slice(start, end);
// generator function loops through all attribute matches on the html string
function* multivals (attr) {
const re = new RegExp(`\\s${attr}="(.*?)"`, 'g');
let match;
while((match = re.exec(html)) !== null) {
// yield each property value found for the attr name
yield match[1];
}
}
// the DOM will contain all attribute names once
const doubleAttributeList = Object.keys(dom.attribs)
// compound attribute names with all found values
.map((attr) => {
const matchIterator = multivals(attr);
return [attr, Array.from(matchIterator)];
})
// filter for doubles
.filter((entry) => entry[1].length > 1);
return new Map(doubleAttributeList);
}
You haven't stated what you want to do once you have found doubles, so they are just returned.
#ccprog answer worked, here is a small ES5 refactor:
var file = 'some file';
var fileContents = fs.readFileSync(file, 'utf8');
var $ = cheerio.load(fileContents, {
useHtmlParser2: true,
withStartIndices: true,
withEndIndices: true
});
function getDuplicateAttributes ($elem) {
var dom = $elem.get(0);
// identify tag text position in fileContents
var start = dom.startIndex;
var end = dom.children.length ? dom.children[0].startIndex : dom.endIndex + 1;
// extract
var html = fileContents.slice(start, end);
// the DOM will contain all attribute names once
return Object.keys(dom.attribs)
// compound attribute names with all found values
.map(function (attr) {
// modify regexp to capture values if needed
var regexp = new RegExp('\\s' + attr + '[\\s>=]', 'g');
return html.match(regexp).length > 1 ? attr : null;
})
// filter for doubles
.filter(function (attr) {
return attr !== null;
});
}
var duplicatedAttrs = getDuplicateAttributes($(".some-elem"));
The code:
removes generator
ES6 to ES5
improve RegExp
use string.match() instead of regexp.exec().

Applying google app script code in selection in google docs

I have the following code that puts bold style some keywords in a whole google document:
function boldKeywords() {
// Words that will be put in bold:
var keywords = ["end", "proc", "fun"];
var document = DocumentApp.getActiveDocument();
var body = document.getBody();
var Style = {};
Style[DocumentApp.Attribute.BOLD] = true;
for (j in keywords) {
var found = body.findText(keywords[j]);
while(found != null) {
var foundText = found.getElement().asText();
var start = found.getStartOffset();
var end = found.getEndOffsetInclusive();
foundText.setAttributes(start, end, Style)
found = body.findText(keywords[j], found);
}
}
}
But I would like the code to put the keywords in bold only in the selected area of the document, for doing that, I tried using the function getSelection(), but I have the problem that this function returns a Range, but for applying findText I need a Body, somebody knows what could I do?
Modified Script
function boldKeywordsInSelection() {
const keywords = ["end", "proc", "fun"];
const document = DocumentApp.getActiveDocument();
const selection = document.getSelection();
// get a list of all the different range elements
const rangeElements = selection.getRangeElements();
const Style = {};
Style[DocumentApp.Attribute.BOLD] = true;
// forEach used here because for in was giving me trouble...
rangeElements.forEach(rangeElement => {
// Each range element has a corresponding element (e.g. paragraph)
const parentElement = rangeElement.getElement();
// fixing the limits of the bold operations depending on the selection
const startLimit = rangeElement.getStartOffset();
const endLimit = rangeElement.getEndOffsetInclusive();
for (j in keywords) {
let found = parentElement.findText(keywords[j]);
// wrapping in try catch to escape the for loop from within the while loop
try {
while (found != null) {
const foundText = found.getElement().asText();
const start = found.getStartOffset();
// Checking if the start of the word is after the start of the selection
if (start < startLimit) {
// If so, then skip to next word
found = parentElement.findText(keywords[j], found);
continue;
}
// Checking if the start of the word is after the end of the selection
// if so, go to next element
if (start > endLimit) throw "out of selection";
const end = found.getEndOffsetInclusive();
foundText.setAttributes(start, end, Style)
found = parentElement.findText(keywords[j], found);
}
} catch (e) {
Logger.log(e)
continue;
}
}
})
}
See the comments in the code for more details.
A getSelection produces a Range object, which contains within it various instances of RangeElement. Each RangeElement makes reference to a parent element, and the index positions within that parent. The parent is the element that the range element is a part of. For example:
This selection spans 3 elements. Therefore the selection has 3 range elements. You can only use the findText method on the whole element, not the range element.
This means that the flow of the script is generally the same, except that you need to go through each element and find the text within each element. Since this will return elements that are outside the selection, you need to keep track of the index positions of the selection and the found element and make sure the found element is within the selection.
References
Range
RangeElement
getSelection()

Categories

Resources