Я хочу выполнить регулярное выражение и заменить только текстовое содержимое (innerText) HTML и, в конце концов, сохранить все элементы HTML (или восстановить их в том виде, в котором они были).
Регулярное выражение не должно проверять элементы HTML, а только текстовое содержимое внутри HTML (innerText, textContent и т. д.).
Выдуманный пример, «подсветка диалогов»
нить:
<html>
<body>
<h1>Hello, World!</h1>
<p id = "aaaa";>"Omae wa moshindeiru."</p>
<p id = "aaaa";>"Naani!"</p>
</body>
</html>
Javascript:
element = document.querySelector('body');
element.innerText = element.innerText
.replace(/[\"“”]([^\"”“\n]+)[\"”“]/g, '\"€€$1××\"');
element.innerHTML = element.innerHTML
.replace(/€€/g, '<span style = "color: red">')
.replace(/××/g, '</span>')
ожидаемый результат:
<html>
<body>
<h1>Hello, World!</h1>
<p id = "aaaa";>"<span style = "color: red;">Omae wa mo shindeiru</span>"</p>
<p id = "aaaa";>"<span style = "color: red;">Naani!</span>"</p>
</body>
</html>
Фактический результат:
<html>
<body>
Hello, World!<br><br>"<span style = "color: red;">Omae wa moshindeiru.</span>"<br><br>"<span style = "color: red;">Naani!</span>"
</body>
</html>
Да, я знаю, что мог бы адаптировать регулярное выражение, но нет. Я просто хочу действовать с текстовым содержимым, а затем восстановить потерянные элементы HTML.
🤔 А знаете ли вы, что...
JavaScript поддерживает модульную структуру, что способствует организации кода на больших проектах.
Цитирую себя из комментария выше...
Решение, основанное исключительно на тексте и регулярных выражениях/заменах, не является подходящим инструментом для таких задач. Что вам нужно, так это комбинация обхода по дереву (в DOM) и тестов на основе регулярных выражений, чтобы захватить все соответствующие текстовые узлы. Отсюда вам потребуются методы узлов, чтобы создать и вставить правильное сочетание соответствующего текстового контента и каждого из его новых и включающих
<span/>
элементов.
Приведенный пример кода реализует ровно две функции:...
collectEveryTextNode
, который называется «путешественник по деревьям», который рекурсивно собирает каждый текстовый узел, начиная с предоставленного элемента или текстового узла или списка узлов,
и replaceWithMatchingMarkerFragment
, которая представляет собой this
контекстно-зависимую функцию, которая создает текстовые узлы или узлы-элементы из предоставленного текстового узла, где последний содержит соответствующую фразу/подстроку хотя бы один раз. Функция заменяет переданный текстовый узел фрагментом документа, к которому добавлен хотя бы один маркерный узел, заключающий соответствующий текст.
const regXQuotedPhrase = /(?<quote>["“”])(?<phrase>[^"“”]+)\k<quote>/;
const matchingTextNodes = collectEveryTextNode(document.body)
.filter(({ nodeValue }) => regXQuotedPhrase.test(nodeValue));
console.info({
matchingTextContents: matchingTextNodes
.map(({ nodeValue }) => nodeValue)
});
matchingTextNodes
.forEach(replaceWithMatchingMarkerFragment, {
/**
* - `forEach`'s 2nd `thisArg` parameter gets
* used as config, where one can provide the
* matching criteria as regular expression and
* a custom element node too which wraps itself
* as marker around each matching text-fragment.
*/
regX: regXQuotedPhrase,
node: document.createElement('mark'),
});
mark {
color: #006;
background-color: #ff0;
}
.as-console-wrapper {
left: auto!important;
width: 55%;
min-height: 100%;
}
<h1>Hello, World!</h1>
<p id = "aaaa"> Foo ... "Omae wa moshindeiru." ... bar. </p>
<p id = "aaaa">bar ... "Naani!" ... baz ... "Naani!" ... biz.</p>
<script>
/**
* - The **treewalker** which recursively collects
* every text-node, starting from either a provided
* (elemen/text) node or a node-list/collection.
*/
function collectEveryTextNode(nodeOrCollection) {
const { ELEMENT_NODE, TEXT_NODE } = Node;
nodeOrCollection = nodeOrCollection || {};
return (nodeOrCollection.nodeType === TEXT_NODE)
? [nodeOrCollection]
: Array
.from(
nodeOrCollection.childNodes ?? nodeOrCollection
)
.reduce((result, childNode) => {
const { nodeType } = childNode;
if (nodeType === TEXT_NODE) {
result
.push(childNode);
} else if (nodeType === ELEMENT_NODE) {
result = result
.concat(
// self recursive call.
collectEveryTextNode(childNode)
);
}
return result;
}, []);
}
/**
* - The `this` context-aware function which creates
* either text- or element-nodes from a provided
* text-node, where the latter contains the matching
* phrase/substring at least once.
* - The passed text-node then gets replaced by a
* document-fragment which has got appended at least
* one marker node that encloses a matching text.
*/
function replaceWithMatchingMarkerFragment(textNode) {
const { regX, node: markerNode } = this;
const { parentNode, nodeValue } = textNode;
const fragment = document.createDocumentFragment();
const nodeList = [];
let text = nodeValue;
let regXResult;
while (regXResult = regX.exec(text)) {
const { index, input } = regXResult;
const quotedPhrase = regXResult[0];
const prePhrase = input.slice(0, index);
if (prePhrase) {
nodeList.push(
document.createTextNode(prePhrase),
);
}
const elmNode = markerNode.cloneNode(true);
elmNode.appendChild(
document.createTextNode(quotedPhrase),
);
nodeList.push(elmNode);
text = input.slice(index + quotedPhrase.length);
}
if (text) {
// equals a `postPhrase`.
nodeList.push(
document.createTextNode(text),
);
}
nodeList.forEach(node => fragment.appendChild(node));
textNode.replaceWith(fragment);
}
</script>