Чистый JavaScript: минималистский
Прежде всего, всегда помните об этом при поиске текста в DOM.
MDN - пробелы в DOM
Эта проблема заставит вас обратить внимание на структуру вашего XML / HTML.
В этом чистом примере JavaScript я учитываю возможность нескольких текстовых узлов , которые могут чередоваться с другими видами узлов . Однако изначально я не делаю суждения о пробелах, оставляя эту задачу фильтрации другому коду.
В этой версии я передаю NodeList
из кода вызова / клиента.
/**
* Gets strings from text nodes. Minimalist. Non-robust. Pre-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
var trueTarget = target - 1,
length = nodeList.length; // Because you may have many child nodes.
for (var i = 0; i < length; i++) {
if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
return nodeList[i].nodeValue; // Done! No need to keep going.
}
}
return null;
}
Конечно, при первом тестировании node.hasChildNodes()
не будет необходимости использовать цикл предварительного тестирования.
/**
* Gets strings from text nodes. Minimalist. Non-robust. Post-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
var trueTarget = target - 1,
length = nodeList.length,
i = 0;
do {
if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
return nodeList[i].nodeValue; // Done! No need to keep going.
}
i++;
} while (i < length);
return null;
}
Чистый JavaScript: надежный
Здесь функция getTextById()
использует две вспомогательные функции: getStringsFromChildren()
и filterWhitespaceLines()
.
getStringsFromChildren ()
/**
* Collects strings from child text nodes.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @version 6.0
* @param parentNode An instance of the Node interface, such as an Element. object.
* @return Array of strings, or null.
* @throws TypeError if the parentNode is not a Node object.
*/
function getStringsFromChildren(parentNode)
{
var strings = [],
nodeList.
length;
if (!parentNode instanceof Node) {
throw new TypeError("The parentNode parameter expects an instance of a Node.");
}
if (!parentNode.hasChildNodes()) {
return null; // We are done. Node may resemble <element></element>
}
nodeList = parentNode.childNodes;
length = nodeList.length;
for (var i = 0; i < length; i++) {
if (nodeList[i].nodeType === Node.TEXT_NODE) {
strings.push(nodeList[i].nodeValue);
}
}
if (strings.length > 0) {
return strings;
}
return null;
}
filterWhitespaceLines ()
/**
* Filters an array of strings to remove whitespace lines.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param textArray a String associated with the id attribute of an Element.
* @return Array of strings that are not lines of whitespace, or null.
* @throws TypeError if the textArray param is not of type Array.
*/
function filterWhitespaceLines(textArray)
{
var filteredArray = [],
whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.
if (!textArray instanceof Array) {
throw new TypeError("The textArray parameter expects an instance of a Array.");
}
for (var i = 0; i < textArray.length; i++) {
if (!whitespaceLine.test(textArray[i])) { // If it is not a line of whitespace.
filteredArray.push(textArray[i].trim()); // Trimming here is fine.
}
}
if (filteredArray.length > 0) {
return filteredArray ; // Leave selecting and joining strings for a specific implementation.
}
return null; // No text to return.
}
getTextById ()
/**
* Gets strings from text nodes. Robust.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param id A String associated with the id property of an Element.
* @return Array of strings, or null.
* @throws TypeError if the id param is not of type String.
* @throws TypeError if the id param cannot be used to find a node by id.
*/
function getTextById(id)
{
var textArray = null; // The hopeful output.
var idDatatype = typeof id; // Only used in an TypeError message.
var node; // The parent node being examined.
try {
if (idDatatype !== "string") {
throw new TypeError("The id argument must be of type String! Got " + idDatatype);
}
node = document.getElementById(id);
if (node === null) {
throw new TypeError("No element found with the id: " + id);
}
textArray = getStringsFromChildren(node);
if (textArray === null) {
return null; // No text nodes found. Example: <element></element>
}
textArray = filterWhitespaceLines(textArray);
if (textArray.length > 0) {
return textArray; // Leave selecting and joining strings for a specific implementation.
}
} catch (e) {
console.log(e.message);
}
return null; // No text to return.
}
Затем возвращаемое значение (массив или ноль) отправляется клиентскому коду, где оно должно быть обработано. Надеемся, что массив должен содержать строковые элементы реального текста, а не строки пробела.
Пустые строки (""
) не возвращены, потому что вам нужен текстовый узел, чтобы правильно указывать наличие действительного текста. Возвращение (""
) может создать ложное впечатление о существовании текстового узла, что заставит кого-то предположить, что он может изменить текст, изменив значение .nodeValue
. Это неверно, потому что текстовый узел не существует в случае пустой строки.
Пример 1 :
<p id="bio"></p> <!-- There is no text node here. Return null. -->
Пример 2 :
<p id="bio">
</p> <!-- There are at least two text nodes ("\n"), here. -->
Проблема возникает, когда вы хотите, чтобы ваш HTML был легко читаем, разнося его. Теперь, несмотря на то, что не существует читаемого человеком корректного текста, все еще есть текстовые узлы с символами новой строки ("\n"
) в их свойствах .nodeValue
.
Люди видят примеры один и два как функционально эквивалентные - пустые элементы, ожидающие заполнения. DOM отличается от человеческого мышления. Вот почему функция getStringsFromChildren()
должна определить, существуют ли текстовые узлы, и собрать значения .nodeValue
в массив.
for (var i = 0; i < length; i++) {
if (nodeList[i].nodeType === Node.TEXT_NODE) {
textNodes.push(nodeList[i].nodeValue);
}
}
Во втором примере два текстовых узла существуют, и getStringFromChildren()
вернет .nodeValue
их обоих ("\n"
). Однако filterWhitespaceLines()
использует регулярное выражение, чтобы отфильтровать строки чистых пробельных символов.
Возвращает ли null
вместо символов новой строки ("\n"
) форму лжи клиенту / вызывающему коду? С человеческой точки зрения нет. С точки зрения DOM, да. Однако проблема здесь заключается в получении текста, а не его редактировании. Нет человеческого текста для возврата к вызывающему коду.
Никогда не знаешь, сколько символов новой строки может появиться в чьем-то HTML. Создание счетчика, который ищет «второго» символа новой строки, ненадежно. Может не существовать.
Конечно, еще дальше, проблема редактирования текста в пустом элементе <p></p>
с дополнительным пробелом (пример 2) может означать уничтожение (возможно, пропуск) всего кроме одного текстового узла между теги абзаца, чтобы гарантировать, что элемент содержит именно то, что он должен отображать.
Независимо от того, за исключением случаев, когда вы делаете что-то необычное, вам понадобится способ определить, какое свойство .nodeValue
текстового узла имеет настоящий, читаемый человеком текст, который вы хотите редактировать. filterWhitespaceLines
ведет нас на полпути.
var whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.
for (var i = 0; i < filteredTextArray.length; i++) {
if (!whitespaceLine.test(textArray[i])) { // If it is not a line of whitespace.
filteredTextArray.push(textArray[i].trim()); // Trimming here is fine.
}
}
В этот момент вы можете получить вывод, похожий на этот:
["Dealing with text nodes is fun.", "Some people just use jQuery."]
Нет никакой гарантии, что эти две строки соседствуют друг с другом в DOM, поэтому объединение их с .join()
может создать неестественную композицию. Вместо этого в коде, который вызывает getTextById()
, вам нужно выбрать, с какой строкой вы хотите работать.
Проверьте вывод.
try {
var strings = getTextById("bio");
if (strings === null) {
// Do something.
} else if (strings.length === 1) {
// Do something with strings[0]
} else { // Could be another else if
// Do something. It all depends on the context.
}
} catch (e) {
console.log(e.message);
}
Можно добавить .trim()
внутри getStringsFromChildren()
, чтобы избавиться от начальных и конечных пробелов (или превратить группу пробелов в строку нулевой длины (""
), но как узнать априори, что каждыйПриложению может потребоваться произойти с текстом (строкой) после его обнаружения? Вы этого не сделаете, поэтому оставьте это для конкретной реализации, и пусть getStringsFromChildren()
будет универсальным.
Могут быть случаи, когда этоуровень специфики (target
и т. д.) не требуется. Это замечательно. В этих случаях используйте простое решение. Однако обобщенный алгоритм позволяет вам приспособиться к простым и сложным ситуациям.