Заменить строку во всем исходном коде документа

#javascript

#javascript

Вопрос:

У меня есть HTML-документ со строкой, которую я хочу заменить. Вот строки ( 9673 ):

 <meta property="og:description" content="***April amp;amp;nbsp;9673amp;amp;nbsp;" />
<span class="agent-phone-number"> 5 9673</span>
<a href="#shownumber" class="js-agent-phone-number"><span class="agent-phone-number" data-mobile="9673">9673</span></a>
<span class="agent-phone-number"> 5 9673</span>
<li><a href="sms:9673" class="js-agent-phone-number-sms"><i class="pgicon pgicon-comment-o"></i>SMS</a></li>
  

После загрузки документа я хочу ввести Javascript для замены строки 9673 на 0000 . Как мне это сделать? Мне нужна только часть Javascript, где она выполняет поиск по всему документу HTMLL и заменяет строку.

Комментарии:

1. Вы спрашиваете, как «внедрить JavaScript» — или как заменить строковое значение в каждом значении атрибута и #text узле — или в обоих?

2. Не спрашивая, как внедрить javascript. Позвольте мне отредактировать мой вопрос.

3. вы говорите о разборе html, верно?

Ответ №1:

Быстрый в написании и медленный в выполнении ответ — злоупотребление innerHTML :

 function replace_9673_everywhere() {
    
    const documentHtml = document.documentElement.innerHTML;
    const newDocumentHtml = documentHtml.replace( /9673/g, '0000' );
    document.documentElement.innerHTML = newDocumentHtml;
}

  

…но это, вероятно, приведет к разрыву документа, потому что это заставит браузер повторно проанализировать все (поэтому любое текущее состояние сценария страницы со ссылками на элементы DOM будет нарушено).


Лучшим подходом является прохождение DOM, поскольку это не вызовет повторный анализ при условии, что вы управляете Element.attributes и Element.textContent #text узлами по отдельности) и избегаете использования innerHTML / outerHTML полностью:

 function replace_9673_everywhere() {
    const rootNode = document.documentElement;
    replace_9673_in_element( rootNode );
}

function replace_9673_in_element( el ) {
    // Replace in attributes:
    for( let i = 0; i < el.attributes.length; i   ) {
        const attr = el.attributes.item(i);
        if( attr.value.includes( '9673' ) ) {
            attr.value = attr.value.replace( /9673/g, '0000' );
        }
    } 

    // Replace in child nodes (not just Element children):
    for( const childNode of el.childNodes ) {
        if( childNode.nodeType === Node.TEXT_NODE ) {
            if( childNode.textContent.includes( '9673' ) ) {
                childNode.textContent = childNode.textContent.replace( /9673/g, '0000' );
            }
        }
        else if( childNode.nodeType === Node.ELEMENT_NODE ) {
            replace_9673_in_element( childNode );
        }
    }
}
  

Комментарии:

1. Вы можете пропустить регулярное выражение и выполнить простую замену All()

2. Поддержка браузера @ShubhamSrivastava для String.prototype.replaceAll по-прежнему отсутствует: caniuse.com/?search=replaceAll (по состоянию на сентябрь 2020 года он поддерживается только браузерами Firefox 77 , Chromium 85 (который был выпущен всего пару недель назад) и Safari 13 , исключая текущие версии Opera и Android Firefox.

3. childNode.nodeType всегда будет число, а не строка, поэтому сравнения всегда будут оцениваться как false . Вместо этого вы можете использовать значения перечисления в разделе Node : childNode.nodeType === Node.TEXT_NODE и childNode.nodeType === Node.ELEMENT_NODE

4. @LionelRowe Сумасшедший, спасибо! Извините, было 3 часа ночи, когда я написал свой ответ.

Ответ №2:

Вот другой подход, использующий TreeWalker s, который нацелен только на текстовые узлы и атрибуты, не нарушая фактическую структуру HTML.

 function* iterateNodes(walker) {
    let next

    while (next = walker.nextNode()) {
        yield next
    }
}

const nodesUnder = (el, nodeFilters) => {
    const walker = document
        .createTreeWalker(el, nodeFilters)

    const nodes = iterateNodes(walker)

    return [...nodes]
}

const htmlEl = document.documentElement

const textNodes = nodesUnder(htmlEl, NodeFilter.SHOW_TEXT)

const attrNodes = nodesUnder(htmlEl, NodeFilter.SHOW_ELEMENT)
    .map(el => [...el.attributes])
    .flat()

;[...textNodes, ...attrNodes].forEach(node => {
    node.nodeValue = node.nodeValue.replace(/9673/g, '0000')
})

console.log(document.querySelector('#outer').outerHTML)  
 * { font-family: sans-serif }  
 <div id="outer">
    <div id="changed-9673" data-attr="9673">9673 (changed)</div>
    <custom-element-9673>unchanged</custom-element-9673>
</div>  

Комментарии:

1. Хороший подход ( 1) — но я чувствую, что использование оператора распространения [...nodes] с вашей пользовательской функцией генератора iterateNodes сводит на нет смысл использования функции генератора с ленивой оценкой, поскольку это сразу загрузит все ссылки на элементы в память. Почему бы просто не return nodes внутри nodesUnder ?

2. Идея заключается в том, что его можно использовать лениво, но вариант использования OP этого не требует. Возможно, это немного переработано для варианта использования, если честно.

Ответ №3:

Вы хотите что-то вроде этого

 function replace9673() {
  $("html").html($("html").html().replaceAll("9673", "0000"));
}  
 <html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<meta property="og:description" content="***April amp;amp;nbsp;9673amp;amp;nbsp;" />
</head>

<body>
<span class="agent-phone-number"> 5 9673</span>
<a href="#shownumber" class="js-agent-phone-number"><span class="agent-phone-number" data-mobile="9673">9673</span></a>
<span class="agent-phone-number"> 5 9673</span>
<li><a href="sms:9673" class="js-agent-phone-number-sms"><i class="pgicon pgicon-comment-o"></i>SMS</a></li>

<button onclick="replace9673()">Click Me</button>
</body>
</html>