Wie erhalte ich den Textknoten eines Elements?

98
<div class="title">
   I am text node
   <a class="edit">Edit</a>
</div>

Ich möchte den "Ich bin Textknoten" erhalten, möchte das "Bearbeiten" -Tag nicht entfernen und benötige eine browserübergreifende Lösung.

Val
quelle
Diese Frage ist ziemlich identisch mit stackoverflow.com/questions/3172166/… - siehe diese Antworten für eine einfache JS-Version von James 'Antwort
Mala

Antworten:

79
var text = $(".title").contents().filter(function() {
  return this.nodeType == Node.TEXT_NODE;
}).text();

Dadurch wird das contentsausgewählte Element abgerufen und eine Filterfunktion darauf angewendet. Die Filterfunktion gibt nur Textknoten zurück (dh die Knoten mit nodeType == Node.TEXT_NODE).

James Allardice
quelle
@Val - Entschuldigung, ich habe das im Originalcode verpasst. Ich werde die Antwort aktualisieren, um sie anzuzeigen. Sie benötigen, text()weil die filterFunktion die Knoten selbst zurückgibt, nicht den Inhalt der Knoten.
James Allardice
1
Ich weiß nicht warum, aber ich bin nicht erfolgreich, wenn ich die obige Theorie teste. Ich habe Folgendes ausgeführt jQuery("*").each(function() { console.log(this.nodeType); })und 1 für alle Knotentypen erhalten.
Batandwa
Ist es möglich, Text am angeklickten Knoten und Text in allen untergeordneten Knoten abzurufen?
Jenna Kwon
Das ist interessant und löst dieses Problem, aber was passiert, wenn die Situation komplexer wird? Es gibt eine flexiblere Möglichkeit, die Arbeit zu erledigen.
Anthony Rutledge
Ohne jQuery, document.querySelector (". Title"). ChildNodes [0] .nodeValue
Balaji Gunasekaran
53

Sie können den nodeValue des ersten childNode mit abrufen

$('.title')[0].childNodes[0].nodeValue

http://jsfiddle.net/TU4FB/

Dogbert
quelle
4
Dies funktioniert zwar, hängt jedoch von der Position der untergeordneten Knoten ab. Wenn (wann) sich das ändert, wird es brechen.
Armstrongest
Wenn der Textknoten nicht das erste untergeordnete Element ist, erhalten Sie möglicherweise nulleinen Rückgabewert.
Anthony Rutledge
14

Wenn Sie den Wert des ersten Textknotens im Element abrufen möchten, funktioniert dieser Code:

var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    if (curNode.nodeName === "#text") {
        firstText = curNode.nodeValue;
        break;
    }
}

Sie können dies hier in Aktion sehen: http://jsfiddle.net/ZkjZJ/

Der Schatten-Assistent ist das Ohr für Sie
quelle
Ich denke, Sie könnten curNode.nodeType == 3stattdessen nodeNameauch verwenden.
Nilloc
1
@Nilloc wahrscheinlich, aber was ist der Gewinn?
Schatten-Assistent ist Ohr für Sie
5
@ShadowWizard @Nilloc empfohlener Weg dafür ist die Verwendung von Konstanten ... curNode.nodeType == Node.TEXT_NODE(numerischer Vergleich ist schneller, aber curNode.nodeType == 3 ist nicht lesbar - welcher Knoten hat Nummer 3?)
Mikep
@ShadowWizard Verwendung curNode.NodeType === Node.TEXT_NODE. Dieser Vergleich findet innerhalb einer Schleife unbekannter möglicher Iterationen statt. Der Vergleich zweier kleiner Zahlen ist besser als der Vergleich von Zeichenfolgen unterschiedlicher Länge (zeitliche und räumliche Überlegungen). Die richtige Frage in dieser Situation lautet "Welche Art / Art von Knoten habe ich?" Und nicht "Welchen Namen habe ich?". developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Anthony Rutledge
2
@ShadowWizard Wenn Sie eine Schleife zum Durchsuchen verwenden möchten, müssen Sie childNodeswissen, dass ein Elementknoten mehr als einen Textknoten haben kann . In einer generischen Lösung muss möglicherweise angegeben werden, auf welche Instanz eines Textknotens innerhalb eines Elementknotens Sie abzielen möchten (die erste, zweite, dritte usw.).
Anthony Rutledge
13

Eine andere native JS-Lösung, die für "komplexe" oder tief verschachtelte Elemente nützlich sein kann, ist die Verwendung von NodeIterator . NodeFilter.SHOW_TEXTGeben Sie als zweites Argument ("whatToShow") an und iterieren Sie nur über die untergeordneten Textknoten des Elements.

var root = document.querySelector('p'),
    iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT),
    textnode;

// print all text nodes
while (textnode = iter.nextNode()) {
  console.log(textnode.textContent)
}
<p>
<br>some text<br>123
</p>

Sie können auch verwenden TreeWalker. Der Unterschied zwischen beiden besteht darin, dass NodeIteratores sich um einen einfachen linearen Iterator handelt, TreeWalkermit dem Sie auch über Geschwister und Vorfahren navigieren können.

Yuval A.
quelle
9

Reines JavaScript: Minimalistisch

Denken Sie immer zuerst daran, wenn Sie im DOM nach Text suchen.

MDN - Leerzeichen im DOM

In diesem Problem werden Sie auf die Struktur Ihres XML / HTML achten.

In diesem reinen JavaScript-Beispiel berücksichtige ich die Möglichkeit mehrerer Textknoten , die mit anderen Arten von Knoten verschachtelt werden könnten . Zunächst beurteile ich Leerzeichen jedoch nicht und überlasse diese Filteraufgabe einem anderen Code.

In dieser Version übergebe ich ein NodeListIn aus dem aufrufenden / Client-Code.

/**
* 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;
}

Wenn Sie node.hasChildNodes()zuerst testen , müssen Sie natürlich keine Vortestschleife verwenden for.

/**
* 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;
}

Reines JavaScript: Robust

Hier verwendet die Funktion getTextById()zwei Hilfsfunktionen: getStringsFromChildren()und filterWhitespaceLines().


getStringsFromChildren ()

/**
* Collects strings from child text nodes.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @version 7.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,
        i = 0;

    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;

    do {
        if ((nodeList[i].nodeType === Node.TEXT_NODE)) {
            strings.push(nodeList[i].nodeValue);
         }

        i++;
    } while (i < length);

    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.
}

Als nächstes wird der Rückgabewert (Array oder null) an den Clientcode gesendet, wo er behandelt werden soll. Hoffentlich sollte das Array Zeichenfolgenelemente aus echtem Text enthalten, keine Leerzeichen.

Leere Zeichenfolgen ( "") werden nicht zurückgegeben, da Sie einen Textknoten benötigen, um das Vorhandensein von gültigem Text ordnungsgemäß anzuzeigen. Die Rückgabe ( "") kann den falschen Eindruck erwecken, dass ein Textknoten vorhanden ist, was dazu führt, dass jemand annimmt, dass er den Text durch Ändern des Werts von ändern kann .nodeValue. Dies ist falsch, da bei einer leeren Zeichenfolge kein Textknoten vorhanden ist.

Beispiel 1 :

<p id="bio"></p> <!-- There is no text node here. Return null. -->

Beispiel 2 :

<p id="bio">

</p> <!-- There are at least two text nodes ("\n"), here. -->

Das Problem tritt auf, wenn Sie Ihren HTML-Code durch Ablesen leicht lesbar machen möchten. Obwohl es keinen von Menschen lesbaren gültigen Text gibt"\n" , gibt es immer noch Textknoten mit newline ( ) -Zeichen in ihren .nodeValueEigenschaften.

Die Menschen sehen die Beispiele eins und zwei als funktional äquivalent - leere Elemente, die darauf warten, gefüllt zu werden. Das DOM unterscheidet sich vom menschlichen Denken. Aus diesem Grund getStringsFromChildren()muss die Funktion feststellen, ob Textknoten vorhanden sind, und die .nodeValueWerte in einem Array zusammenfassen.

for (var i = 0; i < length; i++) {
    if (nodeList[i].nodeType === Node.TEXT_NODE) {
            textNodes.push(nodeList[i].nodeValue);
    }
}

In Beispiel zwei sind zwei Textknoten vorhanden getStringFromChildren(), die .nodeValuebeide zurückgeben ( "\n"). Verwendet filterWhitespaceLines()jedoch einen regulären Ausdruck, um Zeilen mit reinen Leerzeichen herauszufiltern.

Ist das Zurückgeben nullvon Zeichen anstelle von newline ( "\n") eine Form des Lügens des Client- / Aufrufcodes ? In menschlicher Hinsicht nein. In DOM-Begriffen ja. Hier geht es jedoch darum , Text abzurufen und nicht zu bearbeiten. Es gibt keinen menschlichen Text, um zum aufrufenden Code zurückzukehren.

Man kann nie wissen, wie viele Zeilenumbrüche in HTML von jemandem erscheinen könnten. Das Erstellen eines Zählers, der nach dem "zweiten" Zeilenumbruchzeichen sucht, ist unzuverlässig. Es könnte nicht existieren.

Später könnte das Problem der Bearbeitung von Text in einem leeren <p></p>Element mit zusätzlichen Leerzeichen (Beispiel 2) natürlich bedeuten, dass alle bis auf einen Textknoten zwischen den Tags eines Absatzes zerstört (möglicherweise übersprungen) werden, um sicherzustellen, dass das Element genau das enthält, was es ist soll anzeigen.

Unabhängig davon, außer in Fällen, in denen Sie etwas Außergewöhnliches tun, müssen Sie ermitteln, welche .nodeValueEigenschaft des Textknotens den wahren, von Menschen lesbaren Text enthält, den Sie bearbeiten möchten. filterWhitespaceLinesbringt uns auf halbem Weg dorthin.

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. 
    }
}

Zu diesem Zeitpunkt haben Sie möglicherweise eine Ausgabe, die folgendermaßen aussieht:

["Dealing with text nodes is fun.", "Some people just use jQuery."]

Es gibt keine Garantie dafür, dass diese beiden Zeichenfolgen im DOM nebeneinander liegen. Wenn Sie sie also miteinander verbinden, entsteht .join()möglicherweise eine unnatürliche Zusammensetzung. Stattdessen müssen Sie im aufrufenden Code auswählen getTextById(), mit welcher Zeichenfolge Sie arbeiten möchten.

Testen Sie die Ausgabe.

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);
}

Man könnte .trim()innerhalb von hinzufügen getStringsFromChildren(), um führende und nachfolgende Leerzeichen zu entfernen (oder eine Reihe von Leerzeichen in eine Zeichenfolge mit der Länge Null umzuwandeln ( ""), aber wie können Sie a priori wissen, was jede Anwendung möglicherweise mit dem Text (Zeichenfolge) geschehen muss? Sobald es gefunden ist, tun Sie es nicht. Überlassen Sie dies einer bestimmten Implementierung und lassen Sie getStringsFromChildren()es generisch sein.

Es kann Zeiten geben, in denen diese Spezifität (die target und solche) nicht erforderlich ist. Das ist großartig. Verwenden Sie in diesen Fällen eine einfache Lösung. Ein verallgemeinerter Algorithmus ermöglicht es Ihnen jedoch, einfache und komplexe Situationen zu berücksichtigen.

Anthony Rutledge
quelle
8

ES6-Version, die den ersten # Textknoteninhalt zurückgibt

const extract = (node) => {
  const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}
Jujule
quelle
Ich wundere mich über Effizienz und Flexibilität. (1) Die Verwendung von .from(), um eine flach kopierte Array-Instanz zu erstellen. (2) Die Verwendung von .find(), um einen String-Vergleich mit durchzuführen .nodeName. Verwenden node.NodeType === Node.TEXT_NODEwäre besser. (3) Die Rückgabe einer leeren Zeichenfolge, wenn kein Wert vorhanden ist null, ist wahrer, wenn kein Textknoten gefunden wird. Wenn kein Textknoten gefunden wird, muss möglicherweise einer erstellt werden! Wenn Sie eine leere Zeichenfolge zurückgeben, ""können Sie den falschen Eindruck erwecken, dass ein Textknoten vorhanden ist und normal bearbeitet werden kann. Im Wesentlichen ist die Rückgabe einer leeren Zeichenfolge eine Notlüge und wird am besten vermieden.
Anthony Rutledge
(4) Wenn eine Knotenliste mehr als einen Textknoten enthält, können Sie hier nicht angeben, welchen Textknoten Sie möchten. Möglicherweise möchten Sie den ersten Textknoten, aber möglicherweise auch den letzten Textknoten.
Anthony Rutledge
Was schlagen Sie vor, um die Array.from zu ersetzen?
Jujule
@Snowman Bitte fügen Sie Ihre eigene Antwort für solche wesentlichen Änderungen hinzu oder geben Sie Empfehlungen für OP ab, um ihnen die Möglichkeit zu geben, sie in ihre Antwort einzubeziehen.
TylerH
@jujule - Besser zu verwenden [...node.childNodes], um HTMLCollection in Arrays zu konvertieren
vsync
5

.text() - for jquery

$('.title').clone()    //clone the element
.children() //select all the children
.remove()   //remove all the children
.end()  //again go back to selected element
.text();    //get the text of element
Pranay Rana
quelle
1
Ich denke, die Methode für Standard-Javascript muss 'innerText' sein
Reporter
2
Dies funktioniert nicht so, wie es das OP will - es wird auch den Text innerhalb des aElements erhalten: jsfiddle.net/ekHJH
James Allardice
1
@ James Allardice - Ich bin mit der jquery-Lösung fertig, jetzt wird dies funktionieren .................
Pranay Rana
Das wird fast funktionieren, aber Sie vermissen den .am Anfang Ihres Selektors, was bedeutet, dass Sie tatsächlich den Text des titleElements erhalten, nicht Elemente mitclass="title"
James Allardice
@reporter .innerTextist eine alte IE-Konvention, die erst kürzlich übernommen wurde. In Bezug auf Standard-DOM-Skripte node.nodeValueerfasst man den Text eines Textknotens.
Anthony Rutledge
2

Dadurch wird auch das Leerzeichen ignoriert, sodass Sie nie den leeren TextNodes..code mit Javascript erhalten haben.

var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    whitespace = /^\s*$/;
    if (curNode.nodeName === "#text" && !(whitespace.test(curNode.nodeValue))) {
        firstText = curNode.nodeValue;
        break;
    }
}

Überprüfen Sie es auf jsfiddle: - http://jsfiddle.net/webx/ZhLep/

webx
quelle
curNode.nodeType === Node.TEXT_NODEwäre besser. Die Verwendung eines Zeichenfolgenvergleichs und eines regulären Ausdrucks innerhalb einer Schleife ist eine Lösung mit geringer Leistung, insbesondere wenn die Größe oDiv.childNodes.lengthzunimmt. Dieser Algorithmus löst die spezifische Frage des OP, jedoch möglicherweise mit schrecklichen Leistungskosten. Wenn sich die Anordnung oder Anzahl der Textknoten ändert, kann nicht garantiert werden, dass diese Lösung eine genaue Ausgabe zurückgibt. Mit anderen Worten, Sie können nicht genau auf den gewünschten Textknoten abzielen. Sie sind der HTML-Struktur und der Anordnung der Texte dort ausgeliefert.
Anthony Rutledge
1

Sie können auch den XPath- text()Knotentest verwenden, um nur die Textknoten abzurufen. Beispielsweise

var target = document.querySelector('div.title');
var iter = document.evaluate('text()', target, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
var node;
var want = '';

while (node = iter.iterateNext()) {
    want += node.data;
}
doubleDown
quelle
0

Dies ist meine Lösung in ES6, um eine Zeichenfolge zu erstellen, die dem verketteten Text aller untergeordneten Knoten widerspricht (rekursiv) . Beachten Sie, dass Sie auch die shdowroot von Kinderknoten besuchen.

function text_from(node) {
    const extract = (node) => [...node.childNodes].reduce(
        (acc, childnode) => [
            ...acc,
            childnode.nodeType === Node.TEXT_NODE ? childnode.textContent.trim() : '',
            ...extract(childnode),
            ...(childnode.shadowRoot ? extract(childnode.shadowRoot) : [])],
        []);

    return extract(node).filter(text => text.length).join('\n');
}

Diese Lösung wurde von der Lösung von https://stackoverflow.com/a/41051238./1300775 inspiriert .

Damien
quelle